Merge "Pseudo-final api fixes to CalendarContract"
diff --git a/Android.mk b/Android.mk
index 9fbdde7..335fb73 100644
--- a/Android.mk
+++ b/Android.mk
@@ -36,6 +36,7 @@
 # EventLogTags files.
 LOCAL_SRC_FILES += \
        core/java/android/content/EventLogTags.logtags \
+       core/java/android/speech/tts/EventLogTags.logtags \
        core/java/android/webkit/EventLogTags.logtags \
        telephony/java/com/android/internal/telephony/EventLogTags.logtags \
 
diff --git a/api/current.txt b/api/current.txt
index 1e13e24..d507940 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -941,6 +941,7 @@
     field public static final int tension = 16843370; // 0x101026a
     field public static final int testOnly = 16843378; // 0x1010272
     field public static final int text = 16843087; // 0x101014f
+    field public static final int textAllCaps = 16843680; // 0x10103a0
     field public static final int textAppearance = 16842804; // 0x1010034
     field public static final int textAppearanceButton = 16843271; // 0x1010207
     field public static final int textAppearanceInverse = 16842805; // 0x1010035
@@ -26151,6 +26152,7 @@
     method public boolean onTextContextMenuItem(int);
     method public void removeTextChangedListener(android.text.TextWatcher);
     method protected void resetLayoutDirectionResolution();
+    method public void setAllCaps(boolean);
     method public final void setAutoLinkMask(int);
     method public void setCompoundDrawablePadding(int);
     method public void setCompoundDrawables(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 38d0d2a..2666b41 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -23,6 +23,8 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
+import java.util.HashSet;
+
 public final class Bmgr {
     IBackupManager mBmgr;
     IRestoreSession mRestore;
@@ -45,7 +47,6 @@
     }
 
     public void run(String[] args) {
-        boolean validCommand = false;
         if (args.length < 1) {
             showUsage();
             return;
@@ -329,7 +330,13 @@
         } else {
             try {
                 long token = Long.parseLong(arg, 16);
-                doRestoreAll(token);
+                HashSet<String> filter = null;
+                while ((arg = nextArg()) != null) {
+                    if (filter == null) filter = new HashSet<String>();
+                    filter.add(arg);
+                }
+
+                doRestoreAll(token, filter);
             } catch (NumberFormatException e) {
                 showUsage();
                 return;
@@ -364,7 +371,7 @@
         }
     }
 
-    private void doRestoreAll(long token) {
+    private void doRestoreAll(long token, HashSet<String> filter) {
         RestoreObserver observer = new RestoreObserver();
 
         try {
@@ -383,7 +390,13 @@
                     for (RestoreSet s : sets) {
                         if (s.token == token) {
                             System.out.println("Scheduling restore: " + s.name);
-                            didRestore = (mRestore.restoreAll(token, observer) == 0);
+                            if (filter == null) {
+                                didRestore = (mRestore.restoreAll(token, observer) == 0);
+                            } else {
+                                String[] names = new String[filter.size()];
+                                filter.toArray(names);
+                                didRestore = (mRestore.restoreSome(token, observer, names) == 0);
+                            }
                             break;
                         }
                     }
@@ -430,6 +443,7 @@
         System.err.println("       bmgr list sets");
         System.err.println("       bmgr transport WHICH");
         System.err.println("       bmgr restore TOKEN");
+        System.err.println("       bmgr restore TOKEN PACKAGE...");
         System.err.println("       bmgr restore PACKAGE");
         System.err.println("       bmgr run");
         System.err.println("       bmgr wipe PACKAGE");
@@ -457,12 +471,18 @@
         System.err.println("The 'transport' command designates the named transport as the currently");
         System.err.println("active one.  This setting is persistent across reboots.");
         System.err.println("");
-        System.err.println("The 'restore' command when given a restore token initiates a full-system");
+        System.err.println("The 'restore' command when given just a restore token initiates a full-system");
         System.err.println("restore operation from the currently active transport.  It will deliver");
         System.err.println("the restore set designated by the TOKEN argument to each application");
         System.err.println("that had contributed data to that restore set.");
         System.err.println("");
-        System.err.println("The 'restore' command when given a package name intiates a restore of");
+        System.err.println("The 'restore' command when given a token and one or more package names");
+        System.err.println("initiates a restore operation of just those given packages from the restore");
+        System.err.println("set designated by the TOKEN argument.  It is effectively the same as the");
+        System.err.println("'restore' operation supplying only a token, but applies a filter to the");
+        System.err.println("set of applications to be restored.");
+        System.err.println("");
+        System.err.println("The 'restore' command when given just a package name intiates a restore of");
         System.err.println("just that one package according to the restore set selection algorithm");
         System.err.println("used by the RestoreSession.restorePackage() method.");
         System.err.println("");
diff --git a/core/java/android/app/backup/IRestoreSession.aidl b/core/java/android/app/backup/IRestoreSession.aidl
index 1dddbb0..14731ee 100644
--- a/core/java/android/app/backup/IRestoreSession.aidl
+++ b/core/java/android/app/backup/IRestoreSession.aidl
@@ -52,6 +52,25 @@
     int restoreAll(long token, IRestoreObserver observer);
 
     /**
+     * Restore select packages from the given set onto the device, replacing the
+     * current data of any app contained in the set with the data previously
+     * backed up.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @return Zero on success, nonzero on error. The observer will only receive
+     *   progress callbacks if this method returned zero.
+     * @param token The token from {@link getAvailableRestoreSets()} corresponding to
+     *   the restore set that should be used.
+     * @param observer If non-null, this binder points to an object that will receive
+     *   progress callbacks during the restore operation.
+     * @param packages The set of packages for which to attempt a restore.  Regardless of
+     *   the contents of the actual back-end dataset named by {@code token}, only
+     *   applications mentioned in this list will have their data restored.
+     */
+    int restoreSome(long token, IRestoreObserver observer, in String[] packages);
+
+    /**
      * Restore a single application from backup.  The data will be restored from the
      * current backup dataset if the given package has stored data there, or from
      * the dataset used during the last full device setup operation if the current
diff --git a/core/java/android/app/backup/RestoreSession.java b/core/java/android/app/backup/RestoreSession.java
index 24ddb99..7181c61 100644
--- a/core/java/android/app/backup/RestoreSession.java
+++ b/core/java/android/app/backup/RestoreSession.java
@@ -87,6 +87,40 @@
     }
 
     /**
+     * Restore select packages from the given set onto the device, replacing the
+     * current data of any app contained in the set with the data previously
+     * backed up.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @return Zero on success, nonzero on error. The observer will only receive
+     *   progress callbacks if this method returned zero.
+     * @param token The token from {@link getAvailableRestoreSets()} corresponding to
+     *   the restore set that should be used.
+     * @param observer If non-null, this binder points to an object that will receive
+     *   progress callbacks during the restore operation.
+     * @param packages The set of packages for which to attempt a restore.  Regardless of
+     *   the contents of the actual back-end dataset named by {@code token}, only
+     *   applications mentioned in this list will have their data restored.
+     *
+     * @hide
+     */
+    public int restoreSome(long token, RestoreObserver observer, String[] packages) {
+        int err = -1;
+        if (mObserver != null) {
+            Log.d(TAG, "restoreAll() called during active restore");
+            return -1;
+        }
+        mObserver = new RestoreObserverWrapper(mContext, observer);
+        try {
+            err = mBinder.restoreSome(token, mObserver, packages);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Can't contact server to restore packages");
+        }
+        return err;
+    }
+
+    /**
      * Restore a single application from backup.  The data will be restored from the
      * current backup dataset if the given package has stored data there, or from
      * the dataset used during the last full device setup operation if the current
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index c7603ee..8ef4295 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -384,11 +384,16 @@
             }
             count += written;
         }
+
+        param.mLogger.onPlaybackStart();
     }
 
     private void handleSynthesisDone(MessageParams msg) {
         final SynthesisMessageParams params = (SynthesisMessageParams) msg;
         handleSynthesisDone(params);
+        // This call is delayed more than it should be, but we are
+        // certain at this point that we have all the data we want.
+        params.mLogger.onWriteData();
     }
 
     // Flush all remaining data to the audio track, stop it and release
@@ -416,6 +421,8 @@
         final SynthesisMessageParams params = (SynthesisMessageParams) msg;
         if (DBG) Log.d(TAG, "completeAudioAvailable(" + params + ")");
 
+        params.mLogger.onPlaybackStart();
+
         // Channel config and bytes per frame are checked before
         // this message is sent.
         int channelConfig = AudioPlaybackHandler.getChannelConfig(params.mChannelCount);
diff --git a/core/java/android/speech/tts/EventLogTags.logtags b/core/java/android/speech/tts/EventLogTags.logtags
new file mode 100644
index 0000000..1a9f5fe
--- /dev/null
+++ b/core/java/android/speech/tts/EventLogTags.logtags
@@ -0,0 +1,6 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package android.speech.tts;
+
+76001 tts_speak_success (engine|3),(caller|3),(length|1),(locale|3),(rate|1),(pitch|1),(engine_latency|2|3),(engine_total|2|3),(audio_latency|2|3)
+76002 tts_speak_failure (engine|3),(caller|3),(length|1),(locale|3),(rate|1),(pitch|1)
diff --git a/core/java/android/speech/tts/EventLogger.java b/core/java/android/speech/tts/EventLogger.java
new file mode 100644
index 0000000..63b954b
--- /dev/null
+++ b/core/java/android/speech/tts/EventLogger.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.os.SystemClock;
+import android.text.TextUtils;
+
+/**
+ * Writes data about a given speech synthesis request to the event logs.
+ * The data that is logged includes the calling app, length of the utterance,
+ * speech rate / pitch and the latency and overall time taken.
+ *
+ * Note that {@link EventLogger#onStopped()} and {@link EventLogger#onError()}
+ * might be called from any thread, but on {@link EventLogger#onPlaybackStart()} and
+ * {@link EventLogger#onComplete()} must be called from a single thread
+ * (usually the audio playback thread}
+ */
+class EventLogger {
+    private final SynthesisRequest mRequest;
+    private final String mCallingApp;
+    private final String mServiceApp;
+    private final long mReceivedTime;
+    private long mPlaybackStartTime = -1;
+    private volatile long mRequestProcessingStartTime = -1;
+    private volatile long mEngineStartTime = -1;
+    private volatile long mEngineCompleteTime = -1;
+
+    private volatile boolean mError = false;
+    private volatile boolean mStopped = false;
+    private boolean mLogWritten = false;
+
+    EventLogger(SynthesisRequest request, String callingApp,
+            String serviceApp) {
+        mRequest = request;
+        mCallingApp = callingApp;
+        mServiceApp = serviceApp;
+        mReceivedTime = SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * Notifies the logger that this request has been selected from
+     * the processing queue for processing. Engine latency / total time
+     * is measured from this baseline.
+     */
+    public void onRequestProcessingStart() {
+        mRequestProcessingStartTime = SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * Notifies the logger that a chunk of data has been received from
+     * the engine. Might be called multiple times.
+     */
+    public void onEngineDataReceived() {
+        if (mEngineStartTime == -1) {
+            mEngineStartTime = SystemClock.elapsedRealtime();
+        }
+    }
+
+    /**
+     * Notifies the logger that the engine has finished processing data.
+     * Will be called exactly once.
+     */
+    public void onEngineComplete() {
+        mEngineCompleteTime = SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * Notifies the logger that audio playback has started for some section
+     * of the synthesis. This is normally some amount of time after the engine
+     * has synthesized data and varides depending on utterances and
+     * other audio currently in the queue.
+     */
+    public void onPlaybackStart() {
+        // For now, keep track of only the first chunk of audio
+        // that was played.
+        if (mPlaybackStartTime == -1) {
+            mPlaybackStartTime = SystemClock.elapsedRealtime();
+        }
+    }
+
+    /**
+     * Notifies the logger that the current synthesis was stopped.
+     * Latency numbers are not reported for stopped syntheses.
+     */
+    public void onStopped() {
+        mStopped = false;
+    }
+
+    /**
+     * Notifies the logger that the current synthesis resulted in
+     * an error. This is logged using {@link EventLogTags#writeTtsSpeakFailure}.
+     */
+    public void onError() {
+        mError = true;
+    }
+
+    /**
+     * Notifies the logger that the current synthesis has completed.
+     * All available data is not logged.
+     */
+    public void onWriteData() {
+        if (mLogWritten) {
+            return;
+        } else {
+            mLogWritten = true;
+        }
+
+        long completionTime = SystemClock.elapsedRealtime();
+        // onPlaybackStart() should normally always be called if an
+        // error does not occur.
+        if (mError || mPlaybackStartTime == -1 || mEngineCompleteTime == -1) {
+            EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallingApp,
+                    getUtteranceLength(), getLocaleString(),
+                    mRequest.getSpeechRate(), mRequest.getPitch());
+            return;
+        }
+
+        // We don't report stopped syntheses because their overall
+        // total time spent will be innacurate (will not correlate with
+        // the length of the utterance).
+        if (mStopped) {
+            return;
+        }
+
+        final long audioLatency = mPlaybackStartTime - mReceivedTime;
+        final long engineLatency = mEngineStartTime - mRequestProcessingStartTime;
+        final long engineTotal = mEngineCompleteTime - mRequestProcessingStartTime;
+        EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallingApp,
+                getUtteranceLength(), getLocaleString(),
+                mRequest.getSpeechRate(), mRequest.getPitch(),
+                engineLatency, engineTotal, audioLatency);
+    }
+
+    /**
+     * @return the length of the utterance for the given synthesis, 0
+     *          if the utterance was {@code null}.
+     */
+    private int getUtteranceLength() {
+        final String utterance = mRequest.getText();
+        return utterance == null ? 0 : utterance.length();
+    }
+
+    /**
+     * Returns a formatted locale string from the synthesis params of the
+     * form lang-country-variant.
+     */
+    private String getLocaleString() {
+        StringBuilder sb = new StringBuilder(mRequest.getLanguage());
+        if (!TextUtils.isEmpty(mRequest.getCountry())) {
+            sb.append('-');
+            sb.append(mRequest.getCountry());
+
+            if (!TextUtils.isEmpty(mRequest.getVariant())) {
+                sb.append('-');
+                sb.append(mRequest.getVariant());
+            }
+        }
+
+        return sb.toString();
+    }
+
+}
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index bdaa1b8..38030a6 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -65,29 +65,42 @@
 
     private final UtteranceCompletedDispatcher mDispatcher;
     private final String mCallingApp;
+    private final EventLogger mLogger;
 
     PlaybackSynthesisCallback(int streamType, float volume, float pan,
             AudioPlaybackHandler audioTrackHandler, UtteranceCompletedDispatcher dispatcher,
-            String callingApp) {
+            String callingApp, EventLogger logger) {
         mStreamType = streamType;
         mVolume = volume;
         mPan = pan;
         mAudioTrackHandler = audioTrackHandler;
         mDispatcher = dispatcher;
         mCallingApp = callingApp;
+        mLogger = logger;
     }
 
     @Override
     void stop() {
         if (DBG) Log.d(TAG, "stop()");
 
+        // Note that mLogger.mError might be true too at this point.
+        mLogger.onStopped();
+
         synchronized (mStateLock) {
-            if (mToken == null || mStopped) {
-                Log.w(TAG, "stop() called twice, before start(), or after done()");
+            if (mStopped) {
+                Log.w(TAG, "stop() called twice");
                 return;
             }
-            mAudioTrackHandler.stop(mToken);
-            mToken = null;
+            // mToken will be null if the engine encounters
+            // an error before it called start().
+            if (mToken != null) {
+                mAudioTrackHandler.stop(mToken);
+                mToken = null;
+            } else {
+                // In all other cases, mAudioTrackHandler.stop() will
+                // result in onComplete being called.
+                mLogger.onWriteData();
+            }
             mStopped = true;
         }
     }
@@ -124,7 +137,7 @@
             }
             SynthesisMessageParams params = new SynthesisMessageParams(
                     mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
-                    mDispatcher, mCallingApp);
+                    mDispatcher, mCallingApp, mLogger);
             mAudioTrackHandler.enqueueSynthesisStart(params);
 
             mToken = params;
@@ -157,6 +170,8 @@
             mAudioTrackHandler.enqueueSynthesisDataAvailable(mToken);
         }
 
+        mLogger.onEngineDataReceived();
+
         return TextToSpeech.SUCCESS;
     }
 
@@ -177,6 +192,7 @@
             }
 
             mAudioTrackHandler.enqueueSynthesisDone(mToken);
+            mLogger.onEngineComplete();
         }
         return TextToSpeech.SUCCESS;
     }
@@ -184,6 +200,9 @@
     @Override
     public void error() {
         if (DBG) Log.d(TAG, "error() [will call stop]");
+        // Currently, this call will not be logged if error( ) is called
+        // before start.
+        mLogger.onError();
         stop();
     }
 
@@ -208,7 +227,7 @@
             }
             SynthesisMessageParams params = new SynthesisMessageParams(
                     mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
-                    mDispatcher, mCallingApp);
+                    mDispatcher, mCallingApp, mLogger);
             params.addBuffer(buffer, offset, length);
 
             mAudioTrackHandler.enqueueSynthesisCompleteDataAvailable(params);
diff --git a/core/java/android/speech/tts/SynthesisMessageParams.java b/core/java/android/speech/tts/SynthesisMessageParams.java
index 51f3d2e..caf02ef 100644
--- a/core/java/android/speech/tts/SynthesisMessageParams.java
+++ b/core/java/android/speech/tts/SynthesisMessageParams.java
@@ -30,6 +30,7 @@
     final int mChannelCount;
     final float mVolume;
     final float mPan;
+    final EventLogger mLogger;
 
     public volatile AudioTrack mAudioTrack;
 
@@ -38,7 +39,7 @@
     SynthesisMessageParams(int streamType, int sampleRate,
             int audioFormat, int channelCount,
             float volume, float pan, UtteranceCompletedDispatcher dispatcher,
-            String callingApp) {
+            String callingApp, EventLogger logger) {
         super(dispatcher, callingApp);
 
         mStreamType = streamType;
@@ -47,6 +48,7 @@
         mChannelCount = channelCount;
         mVolume = volume;
         mPan = pan;
+        mLogger = logger;
 
         // initially null.
         mAudioTrack = null;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 7ea9373..010c155 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -82,8 +82,7 @@
     private AudioPlaybackHandler mAudioPlaybackHandler;
 
     private CallbackMap mCallbacks;
-
-    private int mDefaultAvailability = TextToSpeech.LANG_NOT_SUPPORTED;
+    private String mPackageName;
 
     @Override
     public void onCreate() {
@@ -99,9 +98,10 @@
 
         mCallbacks = new CallbackMap();
 
+        mPackageName = getApplicationInfo().packageName;
+
         // Load default language
-        mDefaultAvailability = onLoadLanguage(getDefaultLanguage(),
-                getDefaultCountry(), getDefaultVariant());
+        onLoadLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultVariant());
     }
 
     @Override
@@ -457,12 +457,14 @@
         // Non null after synthesis has started, and all accesses
         // guarded by 'this'.
         private AbstractSynthesisCallback mSynthesisCallback;
+        private final EventLogger mEventLogger;
 
         public SynthesisSpeechItem(String callingApp, Bundle params, String text) {
             super(callingApp, params);
             mText = text;
             mSynthesisRequest = new SynthesisRequest(mText, mParams);
             setRequestParams(mSynthesisRequest);
+            mEventLogger = new EventLogger(mSynthesisRequest, getCallingApp(), mPackageName);
         }
 
         public String getText() {
@@ -485,6 +487,7 @@
         @Override
         protected int playImpl() {
             AbstractSynthesisCallback synthesisCallback;
+            mEventLogger.onRequestProcessingStart();
             synchronized (this) {
                 mSynthesisCallback = createSynthesisCallback();
                 synthesisCallback = mSynthesisCallback;
@@ -495,7 +498,7 @@
 
         protected AbstractSynthesisCallback createSynthesisCallback() {
             return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
-                    mAudioPlaybackHandler, this, getCallingApp());
+                    mAudioPlaybackHandler, this, getCallingApp(), mEventLogger);
         }
 
         private void setRequestParams(SynthesisRequest request) {
diff --git a/core/java/android/text/method/AllCapsTransformationMethod.java b/core/java/android/text/method/AllCapsTransformationMethod.java
new file mode 100644
index 0000000..f9920dd
--- /dev/null
+++ b/core/java/android/text/method/AllCapsTransformationMethod.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.text.method;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+
+import java.util.Locale;
+
+/**
+ * Transforms source text into an ALL CAPS string, locale-aware.
+ *
+ * @hide
+ */
+public class AllCapsTransformationMethod implements TransformationMethod2 {
+    private static final String TAG = "AllCapsTransformationMethod";
+
+    private boolean mEnabled;
+    private Locale mLocale;
+
+    public AllCapsTransformationMethod(Context context) {
+        mLocale = context.getResources().getConfiguration().locale;
+    }
+
+    @Override
+    public CharSequence getTransformation(CharSequence source, View view) {
+        if (mEnabled) {
+            return source != null ? source.toString().toUpperCase(mLocale) : null;
+        }
+        Log.w(TAG, "Caller did not enable length changes; not transforming text");
+        return source;
+    }
+
+    @Override
+    public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction,
+            Rect previouslyFocusedRect) {
+    }
+
+    @Override
+    public void setLengthChangesAllowed(boolean allowLengthChanges) {
+        mEnabled = allowLengthChanges;
+    }
+
+}
diff --git a/core/java/android/text/method/TransformationMethod2.java b/core/java/android/text/method/TransformationMethod2.java
new file mode 100644
index 0000000..ef00ecd
--- /dev/null
+++ b/core/java/android/text/method/TransformationMethod2.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.text.method;
+
+/**
+ * TransformationMethod2 extends the TransformationMethod interface
+ * and adds the ability to relax restrictions of TransformationMethod.
+ *
+ * @hide
+ */
+public interface TransformationMethod2 extends TransformationMethod {
+    /**
+     * Relax the contract of TransformationMethod to allow length changes,
+     * or revert to the length-restricted behavior.
+     *
+     * @param allowLengthChanges true to allow the transformation to change the length
+     *                           of the input string.
+     */
+    public void setLengthChangesAllowed(boolean allowLengthChanges);
+}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index b6c5522..4987e2f 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -246,8 +246,19 @@
         return nIsBackBufferPreserved();
     }
 
-    private static native boolean nIsBackBufferPreserved();    
-    
+    private static native boolean nIsBackBufferPreserved();
+
+    /**
+     * Disables v-sync. For performance testing only.
+     * 
+     * @hide
+     */
+    public static void disableVsync() {
+        nDisableVsync();
+    }
+
+    private static native void nDisableVsync();
+
     @Override
     void onPreDraw(Rect dirty) {
         if (dirty != null) {
@@ -265,7 +276,7 @@
     void onPostDraw() {
         nFinish(mRenderer);
     }
-    
+
     private static native void nFinish(int renderer);
 
     @Override
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index bbfb4c1..188970c 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -57,6 +57,16 @@
      * "false", to disable partial invalidates
      */
     static final String RENDER_DIRTY_REGIONS_PROPERTY = "hwui.render_dirty_regions";
+    
+    /**
+     * System property used to enable or disable vsync.
+     * The default value of this property is assumed to be false.
+     * 
+     * Possible values:
+     * "true", to disable vsync
+     * "false", to enable vsync
+     */
+    static final String DISABLE_VSYNC_PROPERTY = "hwui.disable_vsync";
 
     /**
      * Turn on to draw dirty regions every other frame.
@@ -303,6 +313,7 @@
 
         boolean mDirtyRegions;
         final boolean mDirtyRegionsRequested;
+        final boolean mVsyncDisabled;
 
         final int mGlVersion;
         final boolean mTranslucent;
@@ -314,10 +325,17 @@
         GlRenderer(int glVersion, boolean translucent) {
             mGlVersion = glVersion;
             mTranslucent = translucent;
+
             final String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
             //noinspection PointlessBooleanExpression,ConstantConditions
             mDirtyRegions = RENDER_DIRTY_REGIONS && "true".equalsIgnoreCase(dirtyProperty);
             mDirtyRegionsRequested = mDirtyRegions;
+
+            final String vsyncProperty = SystemProperties.get(DISABLE_VSYNC_PROPERTY, "false");
+            mVsyncDisabled = "true".equalsIgnoreCase(vsyncProperty);
+            if (mVsyncDisabled) {
+                Log.d(LOG_TAG, "Disabling v-sync");
+            }
         }
 
         /**
@@ -765,6 +783,14 @@
         }
 
         @Override
+        void setup(int width, int height) {
+            super.setup(width, height);
+            if (mVsyncDisabled) {
+                GLES20Canvas.disableVsync();
+            }
+        }
+
+        @Override
         DisplayList createDisplayList(View v) {
             return new GLES20DisplayList(v);
         }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 41412de..e6fdb17 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -46,7 +46,6 @@
 
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Locale;
 
 /**
  * <p>
@@ -5019,7 +5018,7 @@
      */
     @Override
     protected void resolveTextDirection() {
-        int resolvedTextDirection = TEXT_DIRECTION_UNDEFINED;
+        int resolvedTextDirection;
         switch(mTextDirection) {
             default:
             case TEXT_DIRECTION_INHERIT:
diff --git a/core/java/android/webkit/L10nUtils.java b/core/java/android/webkit/L10nUtils.java
index 4c42cde..f9d0067d 100644
--- a/core/java/android/webkit/L10nUtils.java
+++ b/core/java/android/webkit/L10nUtils.java
@@ -74,7 +74,8 @@
         com.android.internal.R.string.autofill_country_code_re,             // IDS_AUTOFILL_COUNTRY_CODE_RE
         com.android.internal.R.string.autofill_area_code_notext_re,         // IDS_AUTOFILL_AREA_CODE_NOTEXT_RE
         com.android.internal.R.string.autofill_phone_prefix_separator_re,   // IDS_AUTOFILL_PHONE_PREFIX_SEPARATOR_RE
-        com.android.internal.R.string.autofill_phone_suffix_separator_re    // IDS_AUTOFILL_PHONE_SUFFIX_SEPARATOR_RE
+        com.android.internal.R.string.autofill_phone_suffix_separator_re,   // IDS_AUTOFILL_PHONE_SUFFIX_SEPARATOR_RE
+        com.android.internal.R.string.credit_card_number_preview_format     // IDS_CREDIT_CARD_NUMBER_PREVIEW_FORMAT
     };
 
     private static Context mApplicationContext;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 2145edd..c652e55 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -2240,6 +2240,10 @@
     }
 
     private void setupViewport(boolean updateViewState) {
+        if (mWebView == null || mSettings == null) {
+            // We've been destroyed or are being destroyed, return early
+            return;
+        }
         // set the viewport settings from WebKit
         setViewportSettingsFromNative();
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 772e8e9..1e63e26 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -16,6 +16,11 @@
 
 package android.widget;
 
+import com.android.internal.util.FastMath;
+import com.android.internal.widget.EditableInputConnection;
+
+import org.xmlpull.v1.XmlPullParserException;
+
 import android.R;
 import android.content.ClipData;
 import android.content.ClipData.Item;
@@ -62,6 +67,7 @@
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.text.method.AllCapsTransformationMethod;
 import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.DateKeyListener;
 import android.text.method.DateTimeKeyListener;
@@ -76,6 +82,7 @@
 import android.text.method.TextKeyListener;
 import android.text.method.TimeKeyListener;
 import android.text.method.TransformationMethod;
+import android.text.method.TransformationMethod2;
 import android.text.method.WordIterator;
 import android.text.style.ClickableSpan;
 import android.text.style.ParagraphStyle;
@@ -125,11 +132,6 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.RemoteViews.RemoteView;
 
-import com.android.internal.util.FastMath;
-import com.android.internal.widget.EditableInputConnection;
-
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.text.BreakIterator;
@@ -424,6 +426,7 @@
         int textSize = 15;
         int typefaceIndex = -1;
         int styleIndex = -1;
+        boolean allCaps = false;
 
         /*
          * Look the appearance up without checking first if it exists because
@@ -471,6 +474,10 @@
                 case com.android.internal.R.styleable.TextAppearance_textStyle:
                     styleIndex = appearance.getInt(attr, -1);
                     break;
+
+                case com.android.internal.R.styleable.TextAppearance_textAllCaps:
+                    allCaps = appearance.getBoolean(attr, false);
+                    break;
                 }
             }
 
@@ -822,6 +829,10 @@
             case com.android.internal.R.styleable.TextView_suggestionsEnabled:
                 mSuggestionsEnabled = a.getBoolean(attr, true);
                 break;
+
+            case com.android.internal.R.styleable.TextView_textAllCaps:
+                allCaps = a.getBoolean(attr, false);
+                break;
             }
         }
         a.recycle();
@@ -1004,6 +1015,10 @@
         }
         setRawTextSize(textSize);
 
+        if (allCaps) {
+            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
+        }
+
         if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
             setTransformationMethod(PasswordTransformationMethod.getInstance());
             typefaceIndex = MONOSPACE;
@@ -1331,6 +1346,14 @@
 
         mTransformation = method;
 
+        if (method instanceof TransformationMethod2) {
+            TransformationMethod2 method2 = (TransformationMethod2) method;
+            mAllowTransformationLengthChange = !mTextIsSelectable && !(mText instanceof Editable);
+            method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
+        } else {
+            mAllowTransformationLengthChange = false;
+        }
+
         setText(mText);
     }
 
@@ -1775,6 +1798,11 @@
 
         setTypefaceByIndex(typefaceIndex, styleIndex);
         
+        if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
+                false)) {
+            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
+        }
+
         appearance.recycle();
     }
 
@@ -2823,14 +2851,15 @@
         mBufferType = type;
         mText = text;
 
-        if (mTransformation == null)
+        if (mTransformation == null) {
             mTransformed = text;
-        else
+        } else {
             mTransformed = mTransformation.getTransformation(text, this);
+        }
 
         final int textLength = text.length();
 
-        if (text instanceof Spannable) {
+        if (text instanceof Spannable && !mAllowTransformationLengthChange) {
             Spannable sp = (Spannable) text;
 
             // Remove any ChangeWatchers that might have come
@@ -2852,7 +2881,6 @@
 
             if (mTransformation != null) {
                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-
             }
 
             if (mMovement != null) {
@@ -6570,6 +6598,26 @@
     }
 
     /**
+     * Sets the properties of this field to transform input to ALL CAPS
+     * display. This may use a "small caps" formatting if available.
+     * This setting will be ignored if this field is editable or selectable.
+     *
+     * This call replaces the current transformation method. Disabling this
+     * will not necessarily restore the previous behavior from before this
+     * was enabled.
+     *
+     * @see #setTransformationMethod(TransformationMethod)
+     * @attr ref android.R.styleable#TextView_textAllCaps
+     */
+    public void setAllCaps(boolean allCaps) {
+        if (allCaps) {
+            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
+        } else {
+            setTransformationMethod(null);
+        }
+    }
+
+    /**
      * If true, sets the properties of this field (number of lines, horizontally scrolling,
      * transformation method) to be for a single-line input; if false, restores these to the default
      * conditions.
@@ -10254,6 +10302,7 @@
 
     private MovementMethod          mMovement;
     private TransformationMethod    mTransformation;
+    private boolean                 mAllowTransformationLengthChange;
     private ChangeWatcher           mChangeWatcher;
 
     private ArrayList<TextWatcher>  mListeners = null;
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index 2e7ec58..b754d94 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -26,6 +26,9 @@
  * codes with Message.what starting at Protocol.WIFI + 1 and less than or equal to Protocol.WIFI +
  * Protocol.MAX_MESSAGE
  *
+ * NOTE: After a value is created and source released a value shouldn't be changed to
+ * maintain backwards compatibility.
+ *
  * {@hide}
  */
 public class Protocol {
@@ -40,7 +43,7 @@
     public static final int BASE_DHCP                                               = 0x00030000;
     public static final int BASE_DATA_CONNECTION                                    = 0x00040000;
     public static final int BASE_DATA_CONNECTION_AC                                 = 0x00041000;
-    public static final int BASE_DATA_CONNECTION_TRACKER                            = 0x00050000;
+    public static final int BASE_DATA_CONNECTION_TRACKER                            = 0x00042000;
 
     //TODO: define all used protocols
 }
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 3b497e4..09bebae 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -154,12 +154,7 @@
         // populate accessibility description with title
         setContentDescription(title);
 
-        if (mShowTextAllCaps && title != null) {
-            mTextButton.setText(title.toString().toUpperCase(
-                    getContext().getResources().getConfiguration().locale));
-        } else {
-            mTextButton.setText(mTitle);
-        }
+        mTextButton.setText(mTitle);
 
         updateTextButtonVisibility();
     }
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index 2f7adf0..40e5e8a 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -260,7 +260,6 @@
                     if (mTextView == null) {
                         TextView textView = new TextView(getContext(), null,
                                 com.android.internal.R.attr.actionBarTabTextStyle);
-                        textView.setSingleLine();
                         textView.setEllipsize(TruncateAt.END);
                         LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
                                 LayoutParams.WRAP_CONTENT);
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 2106eb4..681f43f 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -115,6 +115,18 @@
     return error == EGL_SUCCESS && value == EGL_BUFFER_PRESERVED;
 }
 
+static void android_view_GLES20Canvas_disableVsync(JNIEnv* env, jobject clazz) {
+    EGLDisplay display = eglGetCurrentDisplay();
+
+    eglGetError();
+    eglSwapInterval(display, 0);
+
+    EGLint error = eglGetError();
+    if (error != EGL_SUCCESS) {
+        RENDERER_LOGD("Could not disable v-sync (%x)", error);
+    }
+}
+
 // ----------------------------------------------------------------------------
 // Constructors
 // ----------------------------------------------------------------------------
@@ -621,7 +633,7 @@
 
     if (layer) {
         jint* storage = env->GetIntArrayElements(layerInfo, NULL);
-        storage[0] = layer->texture;
+        storage[0] = layer->getTexture();
         env->ReleaseIntArrayElements(layerInfo, storage, 0);
     }
 
@@ -634,8 +646,8 @@
 
     if (layer) {
         jint* storage = env->GetIntArrayElements(layerInfo, NULL);
-        storage[0] = layer->width;
-        storage[1] = layer->height;
+        storage[0] = layer->getWidth();
+        storage[1] = layer->getHeight();
         env->ReleaseIntArrayElements(layerInfo, storage, 0);
     }
 
@@ -647,8 +659,8 @@
     LayerRenderer::resizeLayer(layer, width, height);
 
     jint* storage = env->GetIntArrayElements(layerInfo, NULL);
-    storage[0] = layer->width;
-    storage[1] = layer->height;
+    storage[0] = layer->getWidth();
+    storage[1] = layer->getHeight();
     env->ReleaseIntArrayElements(layerInfo, storage, 0);
 }
 
@@ -722,6 +734,7 @@
 #ifdef USE_OPENGL_RENDERER
     { "nIsBackBufferPreserved", "()Z",         (void*) android_view_GLES20Canvas_isBackBufferPreserved },
     { "nPreserveBackBuffer",    "()Z",         (void*) android_view_GLES20Canvas_preserveBackBuffer },
+    { "nDisableVsync",          "()V",         (void*) android_view_GLES20Canvas_disableVsync },
 
     { "nCreateRenderer",    "()I",             (void*) android_view_GLES20Canvas_createRenderer },
     { "nDestroyRenderer",   "(I)V",            (void*) android_view_GLES20Canvas_destroyRenderer },
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2cc8171..39de054 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2810,6 +2810,8 @@
         <attr name="textColorHint" />
         <!-- Color of the links. -->
         <attr name="textColorLink" />
+        <!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->
+        <attr name="textAllCaps" format="boolean" />
     </declare-styleable>
     <declare-styleable name="TextSwitcher">
     </declare-styleable>
@@ -3074,6 +3076,8 @@
         <attr name="textIsSelectable" />
         <!-- Suggestions will be displayed when the user double taps on editable text. -->
         <attr name="suggestionsEnabled" />
+        <!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->
+        <attr name="textAllCaps" />
     </declare-styleable>
     <!-- An <code>input-extras</code> is a container for extra data to supply to
          an input method.  Contains
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2de9cd3..3c23add 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1779,6 +1779,8 @@
   <public type="attr" name="backgroundStacked" />
   <public type="attr" name="backgroundSplit" />
 
+  <public type="attr" name="textAllCaps" />
+
   <public type="style" name="TextAppearance.SuggestionHighlight" />
   <public type="style" name="Theme.Holo.SplitActionBarWhenNarrow" />
   <public type="style" name="Theme.Holo.Light.SplitActionBarWhenNarrow" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 50f8df7..97a8c0b 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2085,6 +2085,10 @@
     <!-- Do not translate. Regex used by AutoFill. -->
     <string name="autofill_phone_suffix_separator_re">^-$</string>
 
+    <!-- Do not translate. Regex used by AutoFill. -->
+    <!-- Ex: ************1234 -->
+    <string name="credit_card_number_preview_format">$1</string>
+
     <!-- Title of an application permission, listed so the user can choose whether
         they want to allow the application to do this. -->
     <string name="permlab_readHistoryBookmarks">read Browser\'s history and bookmarks</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 11016f4..8a8f81a 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1319,6 +1319,7 @@
         <item name="android:textSize">12sp</item>
         <item name="android:textStyle">bold</item>
         <item name="android:textColor">?android:attr/actionMenuTextColor</item>
+        <item name="android:textAllCaps">true</item>
     </style>
 
     <style name="TextAppearance.Holo.Widget.ActionMode">
@@ -1857,6 +1858,7 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">12sp</item>
         <item name="android:textStyle">bold</item>
+        <item name="android:textAllCaps">true</item>
     </style>
 
     <style name="Widget.Holo.ActionMode" parent="Widget.ActionMode">
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 0310bc3..3c2d80d 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -27,6 +27,7 @@
 
 #include "Rect.h"
 #include "SkiaColorFilter.h"
+#include "Texture.h"
 #include "Vertex.h"
 
 namespace android {
@@ -40,14 +41,18 @@
  * A layer has dimensions and is backed by an OpenGL texture or FBO.
  */
 struct Layer {
-    Layer(const uint32_t layerWidth, const uint32_t layerHeight):
-            width(layerWidth), height(layerHeight) {
+    Layer(const uint32_t layerWidth, const uint32_t layerHeight) {
         mesh = NULL;
         meshIndices = NULL;
         meshElementCount = 0;
-        isCacheable = true;
-        isTextureLayer = false;
+        cacheable = true;
+        textureLayer = false;
         renderTarget = GL_TEXTURE_2D;
+        texture.width = layerWidth;
+        texture.height = layerHeight;
+        colorFilter = NULL;
+        firstFilter = true;
+        firstWrap = true;
     }
 
     ~Layer() {
@@ -64,12 +69,152 @@
         regionRect.set(bounds.leftTop().x, bounds.leftTop().y,
                bounds.rightBottom().x, bounds.rightBottom().y);
 
-        const float texX = 1.0f / float(width);
-        const float texY = 1.0f / float(height);
+        const float texX = 1.0f / float(texture.width);
+        const float texY = 1.0f / float(texture.height);
         const float height = layer.getHeight();
         texCoords.set(
                regionRect.left * texX, (height - regionRect.top) * texY,
                regionRect.right * texX, (height - regionRect.bottom) * texY);
+
+        regionRect.translate(layer.left, layer.top);
+    }
+
+    inline uint32_t getWidth() {
+        return texture.width;
+    }
+
+    inline uint32_t getHeight() {
+        return texture.height;
+    }
+
+    void setSize(uint32_t width, uint32_t height) {
+        texture.width = width;
+        texture.height = height;
+    }
+
+    inline void setBlend(bool blend) {
+        texture.blend = blend;
+    }
+
+    inline bool isBlend() {
+        return texture.blend;
+    }
+
+    inline void setAlpha(int alpha) {
+        this->alpha = alpha;
+    }
+
+    inline void setAlpha(int alpha, SkXfermode::Mode mode) {
+        this->alpha = alpha;
+        this->mode = mode;
+    }
+
+    inline int getAlpha() {
+        return alpha;
+    }
+
+    inline SkXfermode::Mode getMode() {
+        return mode;
+    }
+
+    inline void setEmpty(bool empty) {
+        this->empty = empty;
+    }
+
+    inline bool isEmpty() {
+        return empty;
+    }
+
+    inline void setFbo(GLuint fbo) {
+        this->fbo = fbo;
+    }
+
+    inline GLuint getFbo() {
+        return fbo;
+    }
+
+    inline GLuint* getTexturePointer() {
+        return &texture.id;
+    }
+
+    inline GLuint getTexture() {
+        return texture.id;
+    }
+
+    inline GLenum getRenderTarget() {
+        return renderTarget;
+    }
+
+    inline void setRenderTarget(GLenum renderTarget) {
+        this->renderTarget = renderTarget;
+    }
+
+    void setWrap(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false) {
+        if (firstWrap || force || wrapS != texture.wrapS || wrapT != texture.wrapT) {
+            firstWrap = true;
+            texture.setWrap(wrapS, wrapT);
+            if (bindTexture) {
+                glBindTexture(renderTarget, texture.id);
+            }
+            glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS);
+            glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT);
+        }
+    }
+
+    void setFilter(GLenum min, GLenum mag, bool bindTexture = false, bool force = false) {
+        if (firstFilter || force || min != texture.minFilter || mag != texture.magFilter) {
+            firstFilter = false;
+            texture.setFilter(min, mag);
+            if (bindTexture) {
+                glBindTexture(renderTarget, texture.id);
+            }
+            glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min);
+            glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag);
+        }
+    }
+
+    inline bool isCacheable() {
+        return cacheable;
+    }
+
+    inline void setCacheable(bool cacheable) {
+        this->cacheable = cacheable;
+    }
+
+    inline bool isTextureLayer() {
+        return textureLayer;
+    }
+
+    inline void setTextureLayer(bool textureLayer) {
+        this->textureLayer = textureLayer;
+    }
+
+    inline SkiaColorFilter* getColorFilter() {
+        return colorFilter;
+    }
+
+    inline void setColorFilter(SkiaColorFilter* filter) {
+        colorFilter = filter;
+    }
+
+    inline void bindTexture() {
+        glBindTexture(renderTarget, texture.id);
+    }
+
+    inline void generateTexture() {
+        glGenTextures(1, &texture.id);
+    }
+
+    inline void deleteTexture() {
+        if (texture.id) glDeleteTextures(1, &texture.id);
+    }
+
+    inline void allocateTexture(GLenum format, GLenum storage) {
+        glTexImage2D(renderTarget, 0, format, getWidth(), getHeight(), 0, format, storage, NULL);
+    }
+
+    inline mat4& getTexTransform() {
+        return texTransform;
     }
 
     /**
@@ -82,43 +227,6 @@
     Rect texCoords;
 
     /**
-     * Name of the FBO used to render the layer. If the name is 0
-     * this layer is not backed by an FBO, but a simple texture.
-     */
-    GLuint fbo;
-
-    /**
-     * Opacity of the layer.
-     */
-    int alpha;
-    /**
-     * Blending mode of the layer.
-     */
-    SkXfermode::Mode mode;
-    /**
-     * Indicates whether this layer should be blended.
-     */
-    bool blend;
-
-    /**
-     * Indicates whether this layer has been used already.
-     */
-    bool empty;
-
-    /**
-     * Name of the texture used to render the layer.
-     */
-    GLuint texture;
-    /**
-     * Width of the layer texture.
-     */
-    uint32_t width;
-    /**
-     * Height of the layer texture.
-     */
-    uint32_t height;
-
-    /**
      * Dirty region indicating what parts of the layer
      * have been drawn.
      */
@@ -130,37 +238,66 @@
     Rect regionRect;
 
     /**
-     * Color filter used to draw this layer. Optional.
-     */
-    SkiaColorFilter* colorFilter;
-
-    /**
      * If the layer can be rendered as a mesh, this is non-null.
      */
     TextureVertex* mesh;
     uint16_t* meshIndices;
     GLsizei meshElementCount;
 
+private:
+    /**
+     * Name of the FBO used to render the layer. If the name is 0
+     * this layer is not backed by an FBO, but a simple texture.
+     */
+    GLuint fbo;
+
+    /**
+     * Indicates whether this layer has been used already.
+     */
+    bool empty;
+
+    /**
+     * The texture backing this layer.
+     */
+    Texture texture;
+
     /**
      * If set to true (by default), the layer can be reused.
      */
-    bool isCacheable;
+    bool cacheable;
 
     /**
      * When set to true, this layer must be treated as a texture
      * layer.
      */
-    bool isTextureLayer;
+    bool textureLayer;
+
+    /**
+     * Indicates the render target.
+     */
+    GLenum renderTarget;
+
+    /**
+     * Color filter used to draw this layer. Optional.
+     */
+    SkiaColorFilter* colorFilter;
+
+    /**
+     * Opacity of the layer.
+     */
+    int alpha;
+    /**
+     * Blending mode of the layer.
+     */
+    SkXfermode::Mode mode;
 
     /**
      * Optional texture coordinates transform.
      */
     mat4 texTransform;
 
-    /**
-     * Indicates the render target.
-     */
-    GLenum renderTarget;
+    bool firstFilter;
+    bool firstWrap;
 }; // struct Layer
 
 }; // namespace uirenderer
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index b2d795f..1a15e87 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -68,8 +68,8 @@
 
 void LayerCache::deleteLayer(Layer* layer) {
     if (layer) {
-        mSize -= layer->width * layer->height * 4;
-        glDeleteTextures(1, &layer->texture);
+        mSize -= layer->getWidth() * layer->getHeight() * 4;
+        layer->deleteTexture();
         delete layer;
     }
 }
@@ -93,29 +93,23 @@
         mCache.removeAt(index);
 
         layer = entry.mLayer;
-        mSize -= layer->width * layer->height * 4;
+        mSize -= layer->getWidth() * layer->getHeight() * 4;
 
-        LAYER_LOGD("Reusing layer %dx%d", layer->width, layer->height);
+        LAYER_LOGD("Reusing layer %dx%d", layer->getWidth(), layer->getHeight());
     } else {
         LAYER_LOGD("Creating new layer %dx%d", entry.mWidth, entry.mHeight);
 
         layer = new Layer(entry.mWidth, entry.mHeight);
-        layer->blend = true;
-        layer->empty = true;
-        layer->fbo = 0;
-        layer->colorFilter = NULL;
+        layer->setBlend(true);
+        layer->setEmpty(true);
+        layer->setFbo(0);
 
-        glGenTextures(1, &layer->texture);
-        glBindTexture(GL_TEXTURE_2D, layer->texture);
-
+        layer->generateTexture();
+        layer->bindTexture();
+        layer->setFilter(GL_NEAREST, GL_NEAREST);
+        layer->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
         glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
 
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
 #if DEBUG_LAYERS
         size_t size = mCache.size();
         for (size_t i = 0; i < size; i++) {
@@ -133,30 +127,30 @@
     //       size already in the cache, and reuse it instead of creating a new one
 
     LayerEntry entry(width, height);
-    if (entry.mWidth <= layer->width && entry.mHeight <= layer->height) {
+    if (entry.mWidth <= layer->getWidth() && entry.mHeight <= layer->getHeight()) {
         return true;
     }
 
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, layer->texture);
+    uint32_t oldWidth = layer->getWidth();
+    uint32_t oldHeight = layer->getHeight();
 
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, entry.mWidth, entry.mHeight, 0,
-            GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+    glActiveTexture(GL_TEXTURE0);
+    layer->bindTexture();
+    layer->setSize(entry.mWidth, entry.mHeight);
+    layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
 
     if (glGetError() != GL_NO_ERROR) {
+        layer->setSize(oldWidth, oldHeight);
         return false;
     }
 
-    layer->width = entry.mWidth;
-    layer->height = entry.mHeight;
-
     return true;
 }
 
 bool LayerCache::put(Layer* layer) {
-    if (!layer->isCacheable) return false;
+    if (!layer->isCacheable()) return false;
 
-    const uint32_t size = layer->width * layer->height * 4;
+    const uint32_t size = layer->getWidth() * layer->getHeight() * 4;
     // Don't even try to cache a layer that's bigger than the cache
     if (size < mMaxSize) {
         // TODO: Use an LRU
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index d2d5f39..81b8bf3 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -119,7 +119,7 @@
         }
 
         LayerEntry(Layer* layer):
-            mLayer(layer), mWidth(layer->width), mHeight(layer->height) {
+            mLayer(layer), mWidth(layer->getWidth()), mHeight(layer->getHeight()) {
         }
 
         bool operator<(const LayerEntry& rhs) const {
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index e034a868..1fa343b 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -32,9 +32,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void LayerRenderer::prepareDirty(float left, float top, float right, float bottom, bool opaque) {
-    LAYER_RENDERER_LOGD("Rendering into layer, fbo = %d", mLayer->fbo);
+    LAYER_RENDERER_LOGD("Rendering into layer, fbo = %d", mLayer->getFbo());
 
-    glBindFramebuffer(GL_FRAMEBUFFER, mLayer->fbo);
+    glBindFramebuffer(GL_FRAMEBUFFER, mLayer->getFbo());
 
     const float width = mLayer->layer.getWidth();
     const float height = mLayer->layer.getHeight();
@@ -62,14 +62,14 @@
 
     generateMesh();
 
-    LAYER_RENDERER_LOGD("Finished rendering into layer, fbo = %d", mLayer->fbo);
+    LAYER_RENDERER_LOGD("Finished rendering into layer, fbo = %d", mLayer->getFbo());
 
     // No need to unbind our FBO, this will be taken care of by the caller
     // who will invoke OpenGLRenderer::resume()
 }
 
 GLint LayerRenderer::getTargetFbo() {
-    return mLayer->fbo;
+    return mLayer->getFbo();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -128,8 +128,8 @@
     }
     mLayer->meshElementCount = elementCount;
 
-    const float texX = 1.0f / float(mLayer->width);
-    const float texY = 1.0f / float(mLayer->height);
+    const float texX = 1.0f / float(mLayer->getWidth());
+    const float texY = 1.0f / float(mLayer->getHeight());
     const float height = mLayer->layer.getHeight();
 
     TextureVertex* mesh = mLayer->mesh;
@@ -182,40 +182,41 @@
         return NULL;
     }
 
-    layer->fbo = fbo;
+    layer->setFbo(fbo);
     layer->layer.set(0.0f, 0.0f, width, height);
-    layer->texCoords.set(0.0f, height / float(layer->height),
-            width / float(layer->width), 0.0f);
-    layer->alpha = 255;
-    layer->mode = SkXfermode::kSrcOver_Mode;
-    layer->blend = !isOpaque;
-    layer->colorFilter = NULL;
+    layer->texCoords.set(0.0f, height / float(layer->getHeight()),
+            width / float(layer->getWidth()), 0.0f);
+    layer->setAlpha(255, SkXfermode::kSrcOver_Mode);
+    layer->setBlend(!isOpaque);
+    layer->setColorFilter(NULL);
     layer->region.clear();
 
     GLuint previousFbo;
     glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
 
-    glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
-    glBindTexture(GL_TEXTURE_2D, layer->texture);
+    glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo());
+    layer->bindTexture();
 
     // Initialize the texture if needed
-    if (layer->empty) {
-        layer->empty = false;
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, layer->width, layer->height, 0,
-                GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+    if (layer->isEmpty()) {
+        layer->setEmpty(false);
+        layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
 
         if (glGetError() != GL_NO_ERROR) {
             LOGD("Could not allocate texture");
+
             glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-            glDeleteTextures(1, &layer->texture);
             Caches::getInstance().fboCache.put(fbo);
+
+            layer->deleteTexture();
             delete layer;
+
             return NULL;
         }
     }
 
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-            layer->texture, 0);
+            layer->getTexture(), 0);
 
     glDisable(GL_SCISSOR_TEST);
     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
@@ -229,14 +230,14 @@
 
 bool LayerRenderer::resizeLayer(Layer* layer, uint32_t width, uint32_t height) {
     if (layer) {
-        LAYER_RENDERER_LOGD("Resizing layer fbo = %d to %dx%d", layer->fbo, width, height);
+        LAYER_RENDERER_LOGD("Resizing layer fbo = %d to %dx%d", layer->getFbo(), width, height);
 
         if (Caches::getInstance().layerCache.resize(layer, width, height)) {
             layer->layer.set(0.0f, 0.0f, width, height);
-            layer->texCoords.set(0.0f, height / float(layer->height),
-                    width / float(layer->width), 0.0f);
+            layer->texCoords.set(0.0f, height / float(layer->getHeight()),
+                    width / float(layer->getWidth()), 0.0f);
         } else {
-            if (layer->texture) glDeleteTextures(1, &layer->texture);
+            layer->deleteTexture();
             delete layer;
             return false;
         }
@@ -245,37 +246,23 @@
     return true;
 }
 
-static void setTextureParameters(Layer* layer) {
-    glBindTexture(layer->renderTarget, layer->texture);
-
-    glTexParameteri(layer->renderTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameteri(layer->renderTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-    glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-    glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-}
-
 Layer* LayerRenderer::createTextureLayer(bool isOpaque) {
     LAYER_RENDERER_LOGD("Creating new texture layer");
 
     Layer* layer = new Layer(0, 0);
-    layer->isCacheable = false;
-    layer->isTextureLayer = true;
-    layer->blend = !isOpaque;
-    layer->empty = true;
-    layer->fbo = 0;
-    layer->colorFilter = NULL;
-    layer->fbo = 0;
+    layer->setCacheable(false);
+    layer->setTextureLayer(true);
+    layer->setBlend(!isOpaque);
+    layer->setEmpty(true);
+    layer->setFbo(0);
+    layer->setAlpha(255, SkXfermode::kSrcOver_Mode);
     layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f);
     layer->texCoords.set(0.0f, 1.0f, 0.0f, 1.0f);
-    layer->alpha = 255;
-    layer->mode = SkXfermode::kSrcOver_Mode;
-    layer->colorFilter = NULL;
     layer->region.clear();
-    layer->renderTarget = GL_NONE; // see ::updateTextureLayer()
+    layer->setRenderTarget(GL_NONE); // see ::updateTextureLayer()
 
     glActiveTexture(GL_TEXTURE0);
-    glGenTextures(1, &layer->texture);
+    layer->generateTexture();
 
     return layer;
 }
@@ -283,31 +270,32 @@
 void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
         bool isOpaque, GLenum renderTarget, float* transform) {
     if (layer) {
-        layer->blend = !isOpaque;
-        layer->width = width;
-        layer->height = height;
+        layer->setBlend(!isOpaque);
+        layer->setSize(width, height);
         layer->layer.set(0.0f, 0.0f, width, height);
         layer->region.set(width, height);
         layer->regionRect.set(0.0f, 0.0f, width, height);
-        layer->texTransform.load(transform);
+        layer->getTexTransform().load(transform);
 
-        if (renderTarget != layer->renderTarget) {
-            layer->renderTarget = renderTarget;
-            setTextureParameters(layer);
+        if (renderTarget != layer->getRenderTarget()) {
+            layer->setRenderTarget(renderTarget);
+            layer->bindTexture();
+            layer->setFilter(GL_NEAREST, GL_NEAREST, false, true);
+            layer->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, false, true);
         }
     }
 }
 
 void LayerRenderer::destroyLayer(Layer* layer) {
     if (layer) {
-        LAYER_RENDERER_LOGD("Destroying layer, fbo = %d", layer->fbo);
+        LAYER_RENDERER_LOGD("Destroying layer, fbo = %d", layer->getFbo());
 
-        if (layer->fbo) {
-            Caches::getInstance().fboCache.put(layer->fbo);
+        if (layer->getFbo()) {
+            Caches::getInstance().fboCache.put(layer->getFbo());
         }
 
         if (!Caches::getInstance().layerCache.put(layer)) {
-            if (layer->texture) glDeleteTextures(1, &layer->texture);
+            layer->deleteTexture();
             delete layer;
         } else {
             layer->region.clear();
@@ -317,7 +305,7 @@
 
 void LayerRenderer::destroyLayerDeferred(Layer* layer) {
     if (layer) {
-        LAYER_RENDERER_LOGD("Deferring layer destruction, fbo = %d", layer->fbo);
+        LAYER_RENDERER_LOGD("Deferring layer destruction, fbo = %d", layer->getFbo());
 
         Caches::getInstance().deleteLayerDeferred(layer);
     }
@@ -325,7 +313,7 @@
 
 bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
     Caches& caches = Caches::getInstance();
-    if (layer && layer->isTextureLayer && bitmap->width() <= caches.maxTextureSize &&
+    if (layer && layer->isTextureLayer() && bitmap->width() <= caches.maxTextureSize &&
             bitmap->height() <= caches.maxTextureSize) {
 
         GLuint fbo = caches.fboCache.get();
@@ -365,12 +353,11 @@
                 break;
         }
 
-        float alpha = layer->alpha;
-        SkXfermode::Mode mode = layer->mode;
+        float alpha = layer->getAlpha();
+        SkXfermode::Mode mode = layer->getMode();
 
-        layer->mode = SkXfermode::kSrc_Mode;
-        layer->alpha = 255;
-        layer->fbo = fbo;
+        layer->setAlpha(255, SkXfermode::kSrc_Mode);
+        layer->setFbo(fbo);
 
         glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
@@ -381,8 +368,8 @@
         glActiveTexture(GL_TEXTURE0);
         glBindTexture(GL_TEXTURE_2D, texture);
 
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
@@ -399,7 +386,7 @@
             LayerRenderer renderer(layer);
             renderer.setViewport(bitmap->width(), bitmap->height());
             renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f,
-                    bitmap->width(), bitmap->height(), !layer->blend);
+                    bitmap->width(), bitmap->height(), !layer->isBlend());
             if ((error = glGetError()) != GL_NO_ERROR) goto error;
 
             {
@@ -424,9 +411,8 @@
 #endif
 
         glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-        layer->mode = mode;
-        layer->alpha = alpha;
-        layer->fbo = 0;
+        layer->setAlpha(alpha, mode);
+        layer->setFbo(0);
         glDeleteTextures(1, &texture);
         caches.fboCache.put(fbo);
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 1c06a0b..a349121 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -449,12 +449,11 @@
         return false;
     }
 
-    layer->mode = mode;
-    layer->alpha = alpha;
+    layer->setAlpha(alpha, mode);
     layer->layer.set(bounds);
-    layer->texCoords.set(0.0f, bounds.getHeight() / float(layer->height),
-            bounds.getWidth() / float(layer->width), 0.0f);
-    layer->colorFilter = mColorFilter;
+    layer->texCoords.set(0.0f, bounds.getHeight() / float(layer->getHeight()),
+            bounds.getWidth() / float(layer->getWidth()), 0.0f);
+    layer->setColorFilter(mColorFilter);
 
     // Save the layer in the snapshot
     snapshot->flags |= Snapshot::kFlagIsLayer;
@@ -464,12 +463,13 @@
         return createFboLayer(layer, bounds, snapshot, previousFbo);
     } else {
         // Copy the framebuffer into the layer
-        glBindTexture(GL_TEXTURE_2D, layer->texture);
+        layer->bindTexture();
         if (!bounds.isEmpty()) {
-            if (layer->empty) {
-                glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left,
-                        snapshot->height - bounds.bottom, layer->width, layer->height, 0);
-                layer->empty = false;
+            if (layer->isEmpty()) {
+                glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+                        bounds.left, snapshot->height - bounds.bottom,
+                        layer->getWidth(), layer->getHeight(), 0);
+                layer->setEmpty(false);
             } else {
                 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left,
                         snapshot->height - bounds.bottom, bounds.getWidth(), bounds.getHeight());
@@ -485,7 +485,7 @@
 
 bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, sp<Snapshot> snapshot,
         GLuint previousFbo) {
-    layer->fbo = mCaches.fboCache.get();
+    layer->setFbo(mCaches.fboCache.get());
 
 #if RENDER_LAYERS_AS_REGIONS
     snapshot->region = &snapshot->layer->region;
@@ -507,7 +507,7 @@
     clip.translate(-bounds.left, -bounds.top);
 
     snapshot->flags |= Snapshot::kFlagIsFboLayer;
-    snapshot->fbo = layer->fbo;
+    snapshot->fbo = layer->getFbo();
     snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
     snapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
     snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
@@ -516,18 +516,17 @@
     snapshot->orthoMatrix.load(mOrthoMatrix);
 
     // Bind texture to FBO
-    glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
-    glBindTexture(GL_TEXTURE_2D, layer->texture);
+    glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo());
+    layer->bindTexture();
 
     // Initialize the texture if needed
-    if (layer->empty) {
-        layer->empty = false;
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, layer->width, layer->height, 0,
-                GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+    if (layer->isEmpty()) {
+        layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
+        layer->setEmpty(false);
     }
 
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-            layer->texture, 0);
+            layer->getTexture(), 0);
 
 #if DEBUG_LAYERS_AS_REGIONS
     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
@@ -535,8 +534,8 @@
         LOGE("Framebuffer incomplete (GL error code 0x%x)", status);
 
         glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-        glDeleteTextures(1, &layer->texture);
-        mCaches.fboCache.put(layer->fbo);
+        layer->deleteTexture();
+        mCaches.fboCache.put(layer->getFbo());
 
         delete layer;
 
@@ -578,11 +577,11 @@
     Layer* layer = current->layer;
     const Rect& rect = layer->layer;
 
-    if (!fboLayer && layer->alpha < 255) {
+    if (!fboLayer && layer->getAlpha() < 255) {
         drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
-                layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
+                layer->getAlpha() << 24, SkXfermode::kDstIn_Mode, true);
         // Required below, composeLayerRect() will divide by 255
-        layer->alpha = 255;
+        layer->setAlpha(255);
     }
 
     mCaches.unbindMeshBuffer();
@@ -593,18 +592,16 @@
     // drawing only the dirty region
     if (fboLayer) {
         dirtyLayer(rect.left, rect.top, rect.right, rect.bottom, *previous->transform);
-        if (layer->colorFilter) {
-            setupColorFilter(layer->colorFilter);
+        if (layer->getColorFilter()) {
+            setupColorFilter(layer->getColorFilter());
         }
         composeLayerRegion(layer, rect);
-        if (layer->colorFilter) {
+        if (layer->getColorFilter()) {
             resetColorFilter();
         }
-    } else {
-        if (!rect.isEmpty()) {
-            dirtyLayer(rect.left, rect.top, rect.right, rect.bottom);
-            composeLayerRect(layer, rect, true);
-        }
+    } else if (!rect.isEmpty()) {
+        dirtyLayer(rect.left, rect.top, rect.right, rect.bottom);
+        composeLayerRect(layer, rect, true);
     }
 
     if (fboLayer) {
@@ -622,16 +619,16 @@
     // Failing to add the layer to the cache should happen only if the layer is too large
     if (!mCaches.layerCache.put(layer)) {
         LAYER_LOGD("Deleting layer");
-        glDeleteTextures(1, &layer->texture);
+        layer->deleteTexture();
         delete layer;
     }
 }
 
 void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) {
-    float alpha = layer->alpha / 255.0f;
+    float alpha = layer->getAlpha() / 255.0f;
 
     setupDraw();
-    if (layer->renderTarget == GL_TEXTURE_2D) {
+    if (layer->getRenderTarget() == GL_TEXTURE_2D) {
         setupDrawWithTexture();
     } else {
         setupDrawWithExternalTexture();
@@ -639,17 +636,28 @@
     setupDrawTextureTransform();
     setupDrawColor(alpha, alpha, alpha, alpha);
     setupDrawColorFilter();
-    setupDrawBlending(layer->blend || layer->alpha < 255, layer->mode);
+    setupDrawBlending(layer->isBlend() || alpha < 1.0f, layer->getMode());
     setupDrawProgram();
-    setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom);
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
-    if (layer->renderTarget == GL_TEXTURE_2D) {
-        setupDrawTexture(layer->texture);
+    if (layer->getRenderTarget() == GL_TEXTURE_2D) {
+        setupDrawTexture(layer->getTexture());
     } else {
-        setupDrawExternalTexture(layer->texture);
+        setupDrawExternalTexture(layer->getTexture());
     }
-    setupDrawTextureTransformUniforms(layer->texTransform);
+    if (mSnapshot->transform->isPureTranslate() &&
+            layer->getWidth() == (uint32_t) rect.getWidth() &&
+            layer->getHeight() == (uint32_t) rect.getHeight()) {
+        const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
+        const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+
+        layer->setFilter(GL_NEAREST, GL_NEAREST);
+        setupDrawModelView(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
+    } else {
+        layer->setFilter(GL_LINEAR, GL_LINEAR);
+        setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom);
+    }
+    setupDrawTextureTransformUniforms(layer->getTexTransform());
     setupDrawMesh(&mMeshVertices[0].position[0], &mMeshVertices[0].texture[0]);
 
     glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
@@ -658,14 +666,34 @@
 }
 
 void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) {
-    if (!layer->isTextureLayer) {
+    if (!layer->isTextureLayer()) {
         const Rect& texCoords = layer->texCoords;
         resetDrawTextureTexCoords(texCoords.left, texCoords.top,
                 texCoords.right, texCoords.bottom);
 
-        drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
-                layer->alpha / 255.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
-                &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, swap, swap);
+        float x = rect.left;
+        float y = rect.top;
+        bool simpleTransform = mSnapshot->transform->isPureTranslate() &&
+                layer->getWidth() == (uint32_t) rect.getWidth() &&
+                layer->getHeight() == (uint32_t) rect.getHeight();
+
+        if (simpleTransform) {
+            // When we're swapping, the layer is already in screen coordinates
+            if (!swap) {
+                x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
+                y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+            }
+
+            layer->setFilter(GL_NEAREST, GL_NEAREST, true);
+        } else {
+            layer->setFilter(GL_LINEAR, GL_LINEAR, true);
+        }
+
+        drawTextureMesh(x, y, x + rect.getWidth(), y + rect.getHeight(),
+                layer->getTexture(), layer->getAlpha() / 255.0f,
+                layer->getMode(), layer->isBlend(),
+                &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+                GL_TRIANGLE_STRIP, gMeshCount, swap, swap || simpleTransform);
 
         resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
     } else {
@@ -690,9 +718,9 @@
         size_t count;
         const android::Rect* rects = layer->region.getArray(&count);
 
-        const float alpha = layer->alpha / 255.0f;
-        const float texX = 1.0f / float(layer->width);
-        const float texY = 1.0f / float(layer->height);
+        const float alpha = layer->getAlpha() / 255.0f;
+        const float texX = 1.0f / float(layer->getWidth());
+        const float texY = 1.0f / float(layer->getHeight());
         const float height = rect.getHeight();
 
         TextureVertex* mesh = mCaches.getRegionMesh();
@@ -702,13 +730,22 @@
         setupDrawWithTexture();
         setupDrawColor(alpha, alpha, alpha, alpha);
         setupDrawColorFilter();
-        setupDrawBlending(layer->blend || layer->alpha < 255, layer->mode, false);
+        setupDrawBlending(layer->isBlend() || alpha < 1.0f, layer->getMode(), false);
         setupDrawProgram();
         setupDrawDirtyRegionsDisabled();
         setupDrawPureColorUniforms();
         setupDrawColorFilterUniforms();
-        setupDrawTexture(layer->texture);
-        setupDrawModelViewTranslate(rect.left, rect.top, rect.right, rect.bottom);
+        setupDrawTexture(layer->getTexture());
+        if (mSnapshot->transform->isPureTranslate()) {
+            const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
+            const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+
+            layer->setFilter(GL_NEAREST, GL_NEAREST);
+            setupDrawModelViewTranslate(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
+        } else {
+            layer->setFilter(GL_LINEAR, GL_LINEAR);
+            setupDrawModelViewTranslate(rect.left, rect.top, rect.right, rect.bottom);
+        }
         setupDrawMesh(&mesh[0].position[0], &mesh[0].texture[0]);
 
         for (size_t i = 0; i < count; i++) {
@@ -2154,8 +2191,7 @@
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
 
-    layer->alpha = alpha;
-    layer->mode = mode;
+    layer->setAlpha(alpha, mode);
 
 #if RENDER_LAYERS_AS_REGIONS
     if (!layer->region.isEmpty()) {
@@ -2169,13 +2205,23 @@
             setupDrawWithTexture();
             setupDrawColor(a, a, a, a);
             setupDrawColorFilter();
-            setupDrawBlending(layer->blend || layer->alpha < 255, layer->mode, false);
+            setupDrawBlending(layer->isBlend() || a < 1.0f, layer->getMode(), false);
             setupDrawProgram();
-            setupDrawModelViewTranslate(x, y,
-                    x + layer->layer.getWidth(), y + layer->layer.getHeight());
             setupDrawPureColorUniforms();
             setupDrawColorFilterUniforms();
-            setupDrawTexture(layer->texture);
+            setupDrawTexture(layer->getTexture());
+            if (mSnapshot->transform->isPureTranslate()) {
+                x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
+                y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
+
+                layer->setFilter(GL_NEAREST, GL_NEAREST);
+                setupDrawModelViewTranslate(x, y,
+                        x + layer->layer.getWidth(), y + layer->layer.getHeight(), true);
+            } else {
+                layer->setFilter(GL_LINEAR, GL_LINEAR);
+                setupDrawModelViewTranslate(x, y,
+                        x + layer->layer.getWidth(), y + layer->layer.getHeight());
+            }
             setupDrawMesh(&layer->mesh[0].position[0], &layer->mesh[0].texture[0]);
 
             glDrawElements(GL_TRIANGLES, layer->meshElementCount,
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 4922bb3..c6ae326 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -29,8 +29,22 @@
     Texture() {
         cleanup = false;
         bitmapSize = 0;
+
         wrapS = GL_CLAMP_TO_EDGE;
         wrapT = GL_CLAMP_TO_EDGE;
+
+        minFilter = GL_NEAREST;
+        magFilter = GL_NEAREST;
+    }
+
+    void setWrap(GLenum wrapS, GLenum wrapT) {
+        this->wrapS = wrapS;
+        this->wrapT = wrapT;
+    }
+
+    void setFilter(GLenum min, GLenum mag) {
+        minFilter = min;
+        magFilter = mag;
     }
 
     /**
@@ -67,6 +81,12 @@
      */
     GLenum wrapS;
     GLenum wrapT;
+
+    /**
+     * Last filters set on this texture. Defaults to GL_NEAREST.
+     */
+    GLenum minFilter;
+    GLenum magFilter;
 }; // struct Texture
 
 class AutoTexture {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Constants.java b/packages/SystemUI/src/com/android/systemui/recent/Constants.java
new file mode 100644
index 0000000..66f9292
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recent/Constants.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recent;
+
+public class Constants {
+    static final int MAX_ESCAPE_ANIMATION_DURATION = 500; // in ms
+    static final float FADE_CONSTANT = 0.5f; // unitless
+    static final int SNAP_BACK_DURATION = 250; // in ms
+    static final int ESCAPE_VELOCITY = 100; // speed of item required to "curate" it in dp/s
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
index 3dbcc59..fb7a0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
@@ -25,6 +25,7 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.database.DataSetObserver;
 import android.graphics.RectF;
 import android.util.AttributeSet;
@@ -33,6 +34,7 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.LinearInterpolator;
 import android.widget.HorizontalScrollView;
@@ -42,12 +44,9 @@
 
 public class RecentsHorizontalScrollView extends HorizontalScrollView
         implements View.OnClickListener, View.OnTouchListener {
-    private static final float FADE_CONSTANT = 0.5f;
-    private static final int SNAP_BACK_DURATION = 250;
-    private static final int ESCAPE_VELOCITY = 100; // speed of item required to "curate" it
-    private static final String TAG = RecentsPanelView.TAG;
-    private static final float THRESHHOLD = 50;
     private static final boolean DEBUG_INVALIDATE = false;
+    private static final String TAG = RecentsPanelView.TAG;
+    private static final boolean DEBUG = RecentsPanelView.DEBUG;
     private LinearLayout mLinearLayout;
     private ActivityDescriptionAdapter mAdapter;
     private RecentsCallback mCallback;
@@ -56,6 +55,8 @@
     private float mLastY;
     private boolean mDragging;
     private VelocityTracker mVelocityTracker;
+    private float mDensityScale;
+    private float mPagingTouchSlop;
 
     public RecentsHorizontalScrollView(Context context) {
         this(context, null);
@@ -63,6 +64,8 @@
 
     public RecentsHorizontalScrollView(Context context, AttributeSet attrs) {
         super(context, attrs, 0);
+        mDensityScale = getResources().getDisplayMetrics().density;
+        mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
     }
 
     private int scrollPositionOfMostRecent() {
@@ -101,7 +104,8 @@
 
             case MotionEvent.ACTION_MOVE:
                 float delta = ev.getY() - mLastY;
-                if (Math.abs(delta) > THRESHHOLD) {
+                if (DEBUG) Log.v(TAG, "ACTION_MOVE : " + delta);
+                if (Math.abs(delta) > mPagingTouchSlop) {
                     mDragging = true;
                 }
                 break;
@@ -114,13 +118,14 @@
     }
 
     private float getAlphaForOffset(View view, float thumbHeight) {
-        final float fadeHeight = FADE_CONSTANT * thumbHeight;
+        final float fadeHeight = Constants.FADE_CONSTANT * thumbHeight;
         float result = 1.0f;
         if (view.getY() >= thumbHeight) {
             result = 1.0f - (view.getY() - thumbHeight) / fadeHeight;
         } else if (view.getY() < 0.0f) {
             result = 1.0f + (thumbHeight + view.getY()) / fadeHeight;
         }
+        if (DEBUG) Log.v(TAG, "FADE AMOUNT: " + result);
         return result;
     }
 
@@ -155,12 +160,13 @@
                     final float velocityY = velocityTracker.getYVelocity();
                     final float curY = animView.getY();
                     final float newY = (velocityY >= 0.0f ? 1 : -1) * animView.getHeight();
-
+                    final float maxVelocity = Constants.ESCAPE_VELOCITY * mDensityScale;
                     if (Math.abs(velocityY) > Math.abs(velocityX)
-                            && Math.abs(velocityY) > ESCAPE_VELOCITY
+                            && Math.abs(velocityY) > maxVelocity
                             && (velocityY >= 0.0f) == (animView.getY() >= 0)) {
-                        final long duration =
+                        long duration =
                             (long) (Math.abs(newY - curY) * 1000.0f / Math.abs(velocityY));
+                        duration = Math.min(duration, Constants.MAX_ESCAPE_ANIMATION_DURATION);
                         anim = ObjectAnimator.ofFloat(animView, "y", curY, newY);
                         anim.setInterpolator(new LinearInterpolator());
                         final int swipeDirection = animView.getY() >= 0.0f ?
@@ -181,9 +187,10 @@
                         });
                         anim.setDuration(duration);
                     } else { // Animate back to position
-                        final long duration = Math.abs(velocityY) > 0.0f ?
+                        long duration = Math.abs(velocityY) > 0.0f ?
                                 (long) (Math.abs(newY - curY) * 1000.0f / Math.abs(velocityY))
-                                : SNAP_BACK_DURATION;
+                                : Constants.SNAP_BACK_DURATION;
+                        duration = Math.min(duration, Constants.SNAP_BACK_DURATION);
                         anim = ObjectAnimator.ofFloat(animView, "y", animView.getY(), 0.0f);
                         anim.setInterpolator(new DecelerateInterpolator(2.0f));
                         anim.setDuration(duration);
@@ -241,8 +248,15 @@
         setOverScrollEffectPadding(leftPadding, 0);
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mDensityScale = getResources().getDisplayMetrics().density;
+        mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
+    }
+
     private void setOverScrollEffectPadding(int leftPadding, int i) {
-        // TODO Add to RecentsHorizontalScrollView
+        // TODO Add to (Vertical)ScrollView
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 6190c9b..a55fe9c 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -417,11 +417,11 @@
         mActivityDescriptions = getRecentTasks();
         mListAdapter.notifyDataSetInvalidated();
         if (mActivityDescriptions.size() > 0) {
-            Log.v(TAG, "Showing " + mActivityDescriptions.size() + " apps");
+            if (DEBUG) Log.v(TAG, "Showing " + mActivityDescriptions.size() + " apps");
             updateUiElements(getResources().getConfiguration());
         } else {
             // Immediately hide this panel
-            Log.v(TAG, "Nothing to show");
+            if (DEBUG) Log.v(TAG, "Nothing to show");
             hide(false);
         }
     }
@@ -436,7 +436,7 @@
             paint.setAlpha(255);
             final int srcWidth = thumbnail.getWidth();
             final int srcHeight = thumbnail.getHeight();
-            Log.v(TAG, "Source thumb: " + srcWidth + "x" + srcHeight);
+            if (DEBUG) Log.v(TAG, "Source thumb: " + srcWidth + "x" + srcHeight);
             canvas.drawBitmap(thumbnail,
                     new Rect(0, 0, srcWidth-1, srcHeight-1),
                     new RectF(mGlowBitmapPaddingLeftPx, mGlowBitmapPaddingTopPx,
@@ -486,7 +486,7 @@
 
     public void handleSwipe(View view, int direction) {
         ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
-        Log.v(TAG, "Jettison " + ad.label);
+        if (DEBUG) Log.v(TAG, "Jettison " + ad.label);
         mActivityDescriptions.remove(ad);
 
         // Handled by widget containers to enable LayoutTransitions properly
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index 6a962cb..711ffa3 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -25,6 +25,7 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.database.DataSetObserver;
 import android.graphics.RectF;
 import android.util.AttributeSet;
@@ -33,6 +34,7 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.LinearInterpolator;
 import android.widget.LinearLayout;
@@ -42,12 +44,9 @@
 
 public class RecentsVerticalScrollView extends ScrollView
         implements View.OnClickListener, View.OnTouchListener {
-    private static final float FADE_CONSTANT = 0.5f;
-    private static final int SNAP_BACK_DURATION = 250;
-    private static final int ESCAPE_VELOCITY = 100; // speed of item required to "curate" it
     private static final String TAG = RecentsPanelView.TAG;
-    private static final float THRESHHOLD = 50;
     private static final boolean DEBUG_INVALIDATE = false;
+    private static final boolean DEBUG = RecentsPanelView.DEBUG;
     private LinearLayout mLinearLayout;
     private ActivityDescriptionAdapter mAdapter;
     private RecentsCallback mCallback;
@@ -56,6 +55,8 @@
     private float mLastX;
     private boolean mDragging;
     private VelocityTracker mVelocityTracker;
+    private float mDensityScale;
+    private float mPagingTouchSlop;
 
     public RecentsVerticalScrollView(Context context) {
         this(context, null);
@@ -63,6 +64,8 @@
 
     public RecentsVerticalScrollView(Context context, AttributeSet attrs) {
         super(context, attrs, 0);
+        mDensityScale = getResources().getDisplayMetrics().density;
+        mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
     }
 
     private int scrollPositionOfMostRecent() {
@@ -101,8 +104,8 @@
 
             case MotionEvent.ACTION_MOVE:
                 float delta = ev.getX() - mLastX;
-                Log.v(TAG, "ACTION_MOVE : " + delta);
-                if (Math.abs(delta) > THRESHHOLD) {
+                if (DEBUG) Log.v(TAG, "ACTION_MOVE : " + delta);
+                if (Math.abs(delta) > mPagingTouchSlop) {
                     mDragging = true;
                 }
                 break;
@@ -115,14 +118,14 @@
     }
 
     private float getAlphaForOffset(View view, float thumbWidth) {
-        final float fadeWidth = FADE_CONSTANT * thumbWidth;
+        final float fadeWidth = Constants.FADE_CONSTANT * thumbWidth;
         float result = 1.0f;
         if (view.getX() >= thumbWidth) {
             result = 1.0f - (view.getX() - thumbWidth) / fadeWidth;
         } else if (view.getX() < 0.0f) {
             result = 1.0f + (thumbWidth + view.getX()) / fadeWidth;
         }
-        Log.v(TAG, "FADE AMOUNT: " + result);
+        if (DEBUG) Log.v(TAG, "FADE AMOUNT: " + result);
         return result;
     }
 
@@ -157,12 +160,13 @@
                     final float velocityY = velocityTracker.getYVelocity();
                     final float curX = animView.getX();
                     final float newX = (velocityX >= 0.0f ? 1 : -1) * animView.getWidth();
-
+                    final float maxVelocity = Constants.ESCAPE_VELOCITY * mDensityScale;
                     if (Math.abs(velocityX) > Math.abs(velocityY)
-                            && Math.abs(velocityX) > ESCAPE_VELOCITY
+                            && Math.abs(velocityX) > maxVelocity
                             && (velocityX > 0.0f) == (animView.getX() >= 0)) {
-                        final long duration =
+                        long duration =
                             (long) (Math.abs(newX-curX) * 1000.0f / Math.abs(velocityX));
+                        duration = Math.min(duration, Constants.MAX_ESCAPE_ANIMATION_DURATION);
                         anim = ObjectAnimator.ofFloat(animView, "x", curX, newX);
                         anim.setInterpolator(new LinearInterpolator());
                         final int swipeDirection = animView.getX() >= 0.0f ?
@@ -183,9 +187,10 @@
                         });
                         anim.setDuration(duration);
                     } else { // Animate back to position
-                        final long duration = Math.abs(velocityX) > 0.0f ?
+                        long duration = Math.abs(velocityX) > 0.0f ?
                                 (long) (Math.abs(newX-curX) * 1000.0f / Math.abs(velocityX))
-                                : SNAP_BACK_DURATION;
+                                : Constants.SNAP_BACK_DURATION;
+                        duration = Math.min(duration, Constants.SNAP_BACK_DURATION);
                         anim = ObjectAnimator.ofFloat(animView, "x", animView.getX(), 0.0f);
                         anim.setInterpolator(new DecelerateInterpolator(4.0f));
                         anim.setDuration(duration);
@@ -243,6 +248,13 @@
         setOverScrollEffectPadding(leftPadding, 0);
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mDensityScale = getResources().getDisplayMetrics().density;
+        mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
+    }
+
     private void setOverScrollEffectPadding(int leftPadding, int i) {
         // TODO Add to (Vertical)ScrollView
     }
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 6afccec..786f2fa 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -224,6 +224,7 @@
         public PackageInfo pkgInfo;
         public int pmToken; // in post-install restore, the PM's token for this transaction
         public boolean needFullBackup;
+        public String[] filterSet;
 
         RestoreParams(IBackupTransport _transport, IRestoreObserver _obs,
                 long _token, PackageInfo _pkg, int _pmToken, boolean _needFullBackup) {
@@ -233,6 +234,7 @@
             pkgInfo = _pkg;
             pmToken = _pmToken;
             needFullBackup = _needFullBackup;
+            filterSet = null;
         }
 
         RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token,
@@ -243,6 +245,18 @@
             pkgInfo = null;
             pmToken = 0;
             needFullBackup = _needFullBackup;
+            filterSet = null;
+        }
+
+        RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token,
+                String[] _filterSet, boolean _needFullBackup) {
+            transport = _transport;
+            observer = _obs;
+            token = _token;
+            pkgInfo = null;
+            pmToken = 0;
+            needFullBackup = _needFullBackup;
+            filterSet = _filterSet;
         }
     }
 
@@ -404,7 +418,7 @@
                 Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
                 (new PerformRestoreTask(params.transport, params.observer,
                         params.token, params.pkgInfo, params.pmToken,
-                        params.needFullBackup)).run();
+                        params.needFullBackup, params.filterSet)).run();
                 break;
             }
 
@@ -3020,6 +3034,7 @@
         private File mStateDir;
         private int mPmToken;
         private boolean mNeedFullBackup;
+        private HashSet<String> mFilterSet;
 
         class RestoreRequest {
             public PackageInfo app;
@@ -3033,7 +3048,7 @@
 
         PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
                 long restoreSetToken, PackageInfo targetPackage, int pmToken,
-                boolean needFullBackup) {
+                boolean needFullBackup, String[] filterSet) {
             mTransport = transport;
             mObserver = observer;
             mToken = restoreSetToken;
@@ -3041,6 +3056,15 @@
             mPmToken = pmToken;
             mNeedFullBackup = needFullBackup;
 
+            if (filterSet != null) {
+                mFilterSet = new HashSet<String>();
+                for (String pkg : filterSet) {
+                    mFilterSet.add(pkg);
+                }
+            } else {
+                mFilterSet = null;
+            }
+
             try {
                 mStateDir = new File(mBaseStateDir, transport.transportDirName());
             } catch (RemoteException e) {
@@ -3052,7 +3076,8 @@
             long startRealtime = SystemClock.elapsedRealtime();
             if (DEBUG) Slog.v(TAG, "Beginning restore process mTransport=" + mTransport
                     + " mObserver=" + mObserver + " mToken=" + Long.toHexString(mToken)
-                    + " mTargetPackage=" + mTargetPackage + " mPmToken=" + mPmToken);
+                    + " mTargetPackage=" + mTargetPackage + " mFilterSet=" + mFilterSet
+                    + " mPmToken=" + mPmToken);
 
             PackageManagerBackupAgent pmAgent = null;
             int error = -1; // assume error
@@ -3071,6 +3096,22 @@
 
                 List<PackageInfo> agentPackages = allAgentPackages();
                 if (mTargetPackage == null) {
+                    // if there's a filter set, strip out anything that isn't
+                    // present before proceeding
+                    if (mFilterSet != null) {
+                        for (int i = agentPackages.size() - 1; i >= 0; i--) {
+                            final PackageInfo pkg = agentPackages.get(i);
+                            if (! mFilterSet.contains(pkg.packageName)) {
+                                agentPackages.remove(i);
+                            }
+                        }
+                        if (DEBUG) {
+                            Slog.i(TAG, "Post-filter package set for restore:");
+                            for (PackageInfo p : agentPackages) {
+                                Slog.i(TAG, "    " + p);
+                            }
+                        }
+                    }
                     restorePackages.addAll(agentPackages);
                 } else {
                     // Just one package to attempt restore of
@@ -4266,6 +4307,67 @@
             return -1;
         }
 
+        public synchronized int restoreSome(long token, IRestoreObserver observer,
+                String[] packages) {
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+                    "performRestore");
+
+            if (DEBUG) {
+                StringBuilder b = new StringBuilder(128);
+                b.append("restoreSome token=");
+                b.append(Long.toHexString(token));
+                b.append(" observer=");
+                b.append(observer.toString());
+                b.append(" packages=");
+                if (packages == null) {
+                    b.append("null");
+                } else {
+                    b.append('{');
+                    boolean first = true;
+                    for (String s : packages) {
+                        if (!first) {
+                            b.append(", ");
+                        } else first = false;
+                        b.append(s);
+                    }
+                    b.append('}');
+                }
+                Slog.d(TAG, b.toString());
+            }
+
+            if (mEnded) {
+                throw new IllegalStateException("Restore session already ended");
+            }
+
+            if (mRestoreTransport == null || mRestoreSets == null) {
+                Slog.e(TAG, "Ignoring restoreAll() with no restore set");
+                return -1;
+            }
+
+            if (mPackageName != null) {
+                Slog.e(TAG, "Ignoring restoreAll() on single-package session");
+                return -1;
+            }
+
+            synchronized (mQueueLock) {
+                for (int i = 0; i < mRestoreSets.length; i++) {
+                    if (token == mRestoreSets[i].token) {
+                        long oldId = Binder.clearCallingIdentity();
+                        mWakelock.acquire();
+                        Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+                        msg.obj = new RestoreParams(mRestoreTransport, observer, token,
+                                packages, true);
+                        mBackupHandler.sendMessage(msg);
+                        Binder.restoreCallingIdentity(oldId);
+                        return 0;
+                    }
+                }
+            }
+
+            Slog.w(TAG, "Restore token " + Long.toHexString(token) + " not found");
+            return -1;
+        }
+
         public synchronized int restorePackage(String packageName, IRestoreObserver observer) {
             if (DEBUG) Slog.v(TAG, "restorePackage pkg=" + packageName + " obs=" + observer);
 
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
index 9695344..1f24b58 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
@@ -76,8 +76,6 @@
                 + "' APN: '" + mApn.apn
                 + "' proxy: '" + mApn.proxy + "' port: '" + mApn.port);
 
-        setHttpProxy (mApn.proxy, mApn.port);
-
         createTime = -1;
         lastFailTime = -1;
         lastFailCause = FailCause.NONE;
@@ -152,38 +150,6 @@
         Log.d(LOG_TAG, "[" + getName() + "] " + s);
     }
 
-    private void setHttpProxy(String httpProxy, String httpPort) {
-
-        if (DBG) log("set http proxy for"
-                + "' APN: '" + mActiveApnType
-                + "' proxy: '" + mApn.proxy + "' port: '" + mApn.port);
-        if(TextUtils.equals(mActiveApnType, Phone.APN_TYPE_DEFAULT)) {
-            if (httpProxy == null || httpProxy.length() == 0) {
-                phone.setSystemProperty("net.gprs.http-proxy", null);
-                return;
-            }
-
-            if (httpPort == null || httpPort.length() == 0) {
-                httpPort = "8080";     // Default to port 8080
-            }
-
-            phone.setSystemProperty("net.gprs.http-proxy",
-                    "http://" + httpProxy + ":" + httpPort + "/");
-        } else {
-            if (httpProxy == null || httpProxy.length() == 0) {
-                phone.setSystemProperty("net.gprs.http-proxy." + mActiveApnType, null);
-                return;
-            }
-
-            if (httpPort == null || httpPort.length() == 0) {
-                httpPort = "8080";  // Default to port 8080
-            }
-
-            phone.setSystemProperty("net.gprs.http-proxy." + mActiveApnType,
-                    "http://" + httpProxy + ":" + httpPort + "/");
-        }
-    }
-
     private boolean isIpAddress(String address) {
         if (address == null) return false;
 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
index e1ca756..949589f 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
@@ -39,7 +39,10 @@
 import javax.microedition.khronos.egl.EGLDisplay;
 import javax.microedition.khronos.egl.EGLSurface;
 import javax.microedition.khronos.opengles.GL;
-
+import java.io.BufferedOutputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
@@ -57,6 +60,25 @@
 
         mTextureView = new TextureView(this);
         mTextureView.setSurfaceTextureListener(this);
+        mTextureView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Bitmap b = mTextureView.getBitmap(800, 800);
+                BufferedOutputStream out = null;
+                try {
+                    out = new BufferedOutputStream(new FileOutputStream("/sdcard/out.png"));
+                    b.compress(Bitmap.CompressFormat.PNG, 100, out);
+                } catch (FileNotFoundException e) {
+                    e.printStackTrace();
+                } finally {
+                    if (out != null) try {
+                        out.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        });
 
         setContentView(mTextureView, new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
@@ -77,7 +99,7 @@
         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
-                ((View) mTextureView.getParent()).invalidate();
+                mTextureView.invalidate();
             }
         });
         animator.start();
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
index c857ded..634e7e3a 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
@@ -16,7 +16,6 @@
 
 package com.android.test.hwui;
 
-import android.animation.AnimatorSet;
 import android.app.Activity;
 import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
@@ -34,7 +33,6 @@
     private Camera mCamera;
     private TextureView mTextureView;
     private FrameLayout mContent;
-    private AnimatorSet mAnimatorSet;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -53,7 +51,6 @@
             @Override
             public void onClick(View v) {
                 if (mAdded) {
-                    if (mAnimatorSet != null) mAnimatorSet.cancel();
                     mContent.removeView(mTextureView);
                 } else {
                     mContent.addView(mTextureView);
@@ -62,7 +59,9 @@
             }
         });
 
-        mContent.addView(mTextureView, new FrameLayout.LayoutParams(500, 400, Gravity.CENTER));
+        mContent.addView(mTextureView, new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER));
         mContent.addView(button, new FrameLayout.LayoutParams(
                 FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
                 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM));
@@ -72,6 +71,9 @@
     @Override
     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
         mCamera = Camera.open();
+        Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
+        mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
+                previewSize.width, previewSize.height, Gravity.CENTER));
 
         try {
             mCamera.setPreviewTexture(surface);
@@ -82,20 +84,6 @@
         mCamera.startPreview();
 
         mTextureView.setCameraDistance(5000);
-
-//        ObjectAnimator rotationY = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
-//        rotationY.setRepeatMode(ObjectAnimator.REVERSE);
-//        rotationY.setRepeatCount(ObjectAnimator.INFINITE);
-//        rotationY.setDuration(4000);
-
-//        ObjectAnimator alpha = ObjectAnimator.ofFloat(mTextureView, "alpha", 1.0f, 0.0f);
-//        alpha.setRepeatMode(ObjectAnimator.REVERSE);
-//        alpha.setRepeatCount(ObjectAnimator.INFINITE);
-//        alpha.setDuration(4000);
-
-//        mAnimatorSet = new AnimatorSet();
-//        mAnimatorSet.play(alpha).with(rotationY);
-//        mAnimatorSet.start();
     }
 
     @Override