Merge "Remove obsolete recents layout params."
diff --git a/Android.mk b/Android.mk
index 618f404..603f5c3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -160,7 +160,7 @@
 	core/java/android/print/IPrinterDiscoveryObserver.aidl \
 	core/java/android/print/IPrintAdapter.aidl \
 	core/java/android/print/IPrintClient.aidl \
-	core/java/android/print/IPrintProgressListener.aidl \
+	core/java/android/print/IPrintResultCallback.aidl \
 	core/java/android/print/IPrintManager.aidl \
 	core/java/android/print/IPrintSpoolerService.aidl \
 	core/java/android/print/IPrintSpoolerServiceCallbacks.aidl \
diff --git a/api/current.txt b/api/current.txt
index 667f564..a6f2cf5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16899,7 +16899,7 @@
   }
 
   public class Matrix {
-    ctor public Matrix();
+    ctor public deprecated Matrix();
     method public static void frustumM(float[], int, float, float, float, float, float, float);
     method public static boolean invertM(float[], int, float[], int);
     method public static float length(float, float, float);
@@ -18404,12 +18404,12 @@
     ctor public PrintAdapter();
     method public abstract android.print.PrintAdapterInfo getInfo();
     method public void onFinish();
-    method public abstract void onPrint(java.util.List<android.print.PageRange>, java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintAdapter.PrintProgressCallback);
+    method public abstract void onPrint(java.util.List<android.print.PageRange>, java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintAdapter.PrintResultCallback);
     method public boolean onPrintAttributesChanged(android.print.PrintAttributes);
     method public void onStart();
   }
 
-  public static abstract class PrintAdapter.PrintProgressCallback {
+  public static abstract class PrintAdapter.PrintResultCallback {
     method public void onPrintFailed(java.lang.CharSequence);
     method public void onPrintFinished(java.util.List<android.print.PageRange>);
   }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4a41896..066775d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.view.DisplayAdjustments.DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN;
+
 import android.app.backup.BackupAgent;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
@@ -75,7 +77,7 @@
 import android.util.LogPrinter;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
 import android.view.Display;
 import android.view.HardwareRenderer;
 import android.view.View;
@@ -209,8 +211,8 @@
             = new HashMap<String, WeakReference<LoadedApk>>();
     final HashMap<String, WeakReference<LoadedApk>> mResourcePackages
             = new HashMap<String, WeakReference<LoadedApk>>();
-    final HashMap<CompatibilityInfo, DisplayMetrics> mDefaultDisplayMetrics
-            = new HashMap<CompatibilityInfo, DisplayMetrics>();
+    final HashMap<DisplayAdjustments, DisplayMetrics> mDefaultDisplayMetrics
+            = new HashMap<DisplayAdjustments, DisplayMetrics>();
     final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
             = new HashMap<ResourcesKey, WeakReference<Resources> >();
     final ArrayList<ActivityClientRecord> mRelaunchingActivities
@@ -1554,6 +1556,7 @@
     }
 
     private class Idler implements MessageQueue.IdleHandler {
+        @Override
         public final boolean queueIdle() {
             ActivityClientRecord a = mNewActivities;
             boolean stopProfiling = false;
@@ -1592,6 +1595,7 @@
     }
 
     final class GcIdler implements MessageQueue.IdleHandler {
+        @Override
         public final boolean queueIdle() {
             doGcIfNeeded();
             return false;
@@ -1604,8 +1608,10 @@
         final private Configuration mOverrideConfiguration;
         final private float mScale;
         final private int mHash;
+        final private IBinder mToken;
 
-        ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) {
+        ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration,
+                float scale, IBinder token) {
             mResDir = resDir;
             mDisplayId = displayId;
             if (overrideConfiguration != null) {
@@ -1621,6 +1627,12 @@
             hash = 31 * hash + (mOverrideConfiguration != null
                     ? mOverrideConfiguration.hashCode() : 0);
             hash = 31 * hash + Float.floatToIntBits(mScale);
+            if (DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN) {
+                mToken = token;
+                hash = 31 * hash + (mToken == null ? 0 : mToken.hashCode());
+            } else {
+                mToken = null;
+            }
             mHash = hash;
         }
 
@@ -1693,9 +1705,13 @@
         mDefaultDisplayMetrics.clear();
     }
 
-    DisplayMetrics getDisplayMetricsLocked(int displayId, CompatibilityInfo ci) {
+    DisplayMetrics getDisplayMetricsLocked(int displayId) {
+        return getDisplayMetricsLocked(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+    }
+
+    DisplayMetrics getDisplayMetricsLocked(int displayId, DisplayAdjustments daj) {
         boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
-        DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(ci) : null;
+        DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(daj) : null;
         if (dm != null) {
             return dm;
         }
@@ -1709,12 +1725,10 @@
         }
 
         if (isDefaultDisplay) {
-            mDefaultDisplayMetrics.put(ci, dm);
+            mDefaultDisplayMetrics.put(daj, dm);
         }
 
-        CompatibilityInfoHolder cih = new CompatibilityInfoHolder();
-        cih.set(ci);
-        Display d = displayManager.getCompatibleDisplay(displayId, cih);
+        Display d = displayManager.getCompatibleDisplay(displayId, daj);
         if (d != null) {
             d.getMetrics(dm);
         } else {
@@ -1736,7 +1750,7 @@
         if (config == null) {
             return null;
         }
-        if (compat != null && !compat.supportsScreen()) {
+        if (!compat.supportsScreen()) {
             mMainThreadConfig.setTo(config);
             config = mMainThreadConfig;
             compat.applyToConfiguration(displayDensity, config);
@@ -1748,21 +1762,19 @@
      * Creates the top level Resources for applications with the given compatibility info.
      *
      * @param resDir the resource directory.
-     * @param compInfo the compability info. It will use the default compatibility info when it's
-     * null.
+     * @param compatInfo the compability info. Must not be null.
+     * @param token the application token for determining stack bounds.
      */
-    Resources getTopLevelResources(String resDir,
-            int displayId, Configuration overrideConfiguration,
-            CompatibilityInfo compInfo) {
-        ResourcesKey key = new ResourcesKey(resDir,
-                displayId, overrideConfiguration,
-                compInfo.applicationScale);
+    Resources getTopLevelResources(String resDir, int displayId,
+            Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
+        final float scale = compatInfo.applicationScale;
+        ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
+                token);
         Resources r;
         synchronized (mPackages) {
             // Resources is app scale dependent.
             if (false) {
-                Slog.w(TAG, "getTopLevelResources: " + resDir + " / "
-                        + compInfo.applicationScale);
+                Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
             }
             WeakReference<Resources> wr = mActiveResources.get(key);
             r = wr != null ? wr.get() : null;
@@ -1787,7 +1799,7 @@
         }
 
         //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
-        DisplayMetrics dm = getDisplayMetricsLocked(displayId, null);
+        DisplayMetrics dm = getDisplayMetricsLocked(displayId);
         Configuration config;
         boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
         if (!isDefaultDisplay || key.mOverrideConfiguration != null) {
@@ -1801,7 +1813,7 @@
         } else {
             config = getConfiguration();
         }
-        r = new Resources(assets, dm, config, compInfo);
+        r = new Resources(assets, dm, config, compatInfo, token);
         if (false) {
             Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
                     + r.getConfiguration() + " appScale="
@@ -1831,7 +1843,7 @@
             int displayId, Configuration overrideConfiguration,
             LoadedApk pkgInfo) {
         return getTopLevelResources(resDir, displayId, overrideConfiguration,
-                pkgInfo.mCompatibilityInfo.get());
+                pkgInfo.getCompatibilityInfo(), null);
     }
 
     final Handler getHandler() {
@@ -2005,10 +2017,8 @@
                 LoadedApk info = new LoadedApk(this, "android", context, null,
                         CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);
                 context.init(info, null, this);
-                context.getResources().updateConfiguration(
-                        getConfiguration(), getDisplayMetricsLocked(
-                                Display.DEFAULT_DISPLAY,
-                                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO));
+                context.getResources().updateConfiguration(getConfiguration(),
+                        getDisplayMetricsLocked(Display.DEFAULT_DISPLAY));
                 mSystemContext = context;
                 //Slog.i(TAG, "Created system resources " + context.getResources()
                 //        + ": " + context.getResources().getConfiguration());
@@ -2034,7 +2044,7 @@
             dalvik.system.VMRuntime.getRuntime().startJitCompilation();
         }
     }
-    
+
     void scheduleGcIdler() {
         if (!mGcIdlerScheduled) {
             mGcIdlerScheduled = true;
@@ -2301,7 +2311,7 @@
             DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
             for (int displayId : dm.getDisplayIds()) {
                 if (displayId != Display.DEFAULT_DISPLAY) {
-                    Display display = dm.getRealDisplay(displayId);
+                    Display display = dm.getRealDisplay(displayId, r.token);
                     baseContext = appContext.createDisplayContext(display);
                     break;
                 }
@@ -3408,11 +3418,11 @@
     private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) {
         LoadedApk apk = peekPackageInfo(data.pkg, false);
         if (apk != null) {
-            apk.mCompatibilityInfo.set(data.info);
+            apk.setCompatibilityInfo(data.info);
         }
         apk = peekPackageInfo(data.pkg, true);
         if (apk != null) {
-            apk.mCompatibilityInfo.set(data.info);
+            apk.setCompatibilityInfo(data.info);
         }
         handleConfigurationChanged(mConfiguration, data.info);
         WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration);
@@ -3844,8 +3854,9 @@
                 for (ActivityClientRecord ar : mActivities.values()) {
                     Activity a = ar.activity;
                     if (a != null) {
-                        Configuration thisConfig = applyConfigCompatMainThread(mCurDefaultDisplayDpi,
-                                newConfig, ar.packageInfo.mCompatibilityInfo.getIfNeeded());
+                        Configuration thisConfig = applyConfigCompatMainThread(
+                                mCurDefaultDisplayDpi, newConfig,
+                                ar.packageInfo.getCompatibilityInfo());
                         if (!ar.activity.mFinished && (allActivities || !ar.paused)) {
                             // If the activity is currently resumed, its configuration
                             // needs to change right now.
@@ -3945,8 +3956,7 @@
         }
         int changes = mResConfiguration.updateFrom(config);
         flushDisplayMetricsLocked();
-        DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(
-                Display.DEFAULT_DISPLAY, null);
+        DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
 
         if (compat != null && (mResCompatibilityInfo == null ||
                 !mResCompatibilityInfo.equals(compat))) {
@@ -3986,7 +3996,7 @@
                     }
                     tmpConfig.setTo(config);
                     if (!isDefaultDisplay) {
-                        dm = getDisplayMetricsLocked(displayId, null);
+                        dm = getDisplayMetricsLocked(displayId);
                         applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
                     }
                     if (overrideConfig != null) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 992d8b7..155aac1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -99,7 +99,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.WindowManagerImpl;
@@ -205,6 +205,8 @@
 
     private static final String[] EMPTY_FILE_LIST = {};
 
+    final private DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
+
     /**
      * Override this class when the system service constructor needs a
      * ContextImpl.  Else, use StaticServiceFetcher below.
@@ -1830,10 +1832,8 @@
 
         ContextImpl c = new ContextImpl();
         c.init(mPackageInfo, null, mMainThread);
-        c.mResources = mMainThread.getTopLevelResources(
-                mPackageInfo.getResDir(),
-                getDisplayId(), overrideConfiguration,
-                mResources.getCompatibilityInfo());
+        c.mResources = mMainThread.getTopLevelResources(mPackageInfo.getResDir(), getDisplayId(),
+                overrideConfiguration, mResources.getCompatibilityInfo(), mActivityToken);
         return c;
     }
 
@@ -1844,17 +1844,13 @@
         }
 
         int displayId = display.getDisplayId();
-        CompatibilityInfo ci = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
-        CompatibilityInfoHolder cih = getCompatibilityInfo(displayId);
-        if (cih != null) {
-            ci = cih.get();
-        }
 
         ContextImpl context = new ContextImpl();
         context.init(mPackageInfo, null, mMainThread);
         context.mDisplay = display;
-        context.mResources = mMainThread.getTopLevelResources(
-                mPackageInfo.getResDir(), displayId, null, ci);
+        DisplayAdjustments daj = getDisplayAdjustments(displayId);
+        context.mResources = mMainThread.getTopLevelResources(mPackageInfo.getResDir(), displayId,
+                null, daj.getCompatibilityInfo(), null);
         return context;
     }
 
@@ -1868,8 +1864,8 @@
     }
 
     @Override
-    public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
-        return displayId == Display.DEFAULT_DISPLAY ? mPackageInfo.mCompatibilityInfo : null;
+    public DisplayAdjustments getDisplayAdjustments(int displayId) {
+        return mDisplayAdjustments;
     }
 
     private File getDataDirFile() {
@@ -1921,6 +1917,7 @@
         mUser = context.mUser;
         mDisplay = context.mDisplay;
         mOuterContext = this;
+        mDisplayAdjustments.setCompatibilityInfo(mPackageInfo.getCompatibilityInfo());
     }
 
     final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) {
@@ -1933,16 +1930,26 @@
         mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
         mResources = mPackageInfo.getResources(mainThread);
 
-        if (mResources != null && container != null
-                && container.getCompatibilityInfo().applicationScale !=
-                        mResources.getCompatibilityInfo().applicationScale) {
+        CompatibilityInfo compatInfo =
+                container == null ? null : container.getCompatibilityInfo();
+        if (mResources != null &&
+                ((compatInfo != null && compatInfo.applicationScale !=
+                        mResources.getCompatibilityInfo().applicationScale)
+                || activityToken != null)) {
             if (DEBUG) {
                 Log.d(TAG, "loaded context has different scaling. Using container's" +
                         " compatiblity info:" + container.getDisplayMetrics());
             }
-            mResources = mainThread.getTopLevelResources(
-                    mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY,
-                    null, container.getCompatibilityInfo());
+            if (compatInfo == null) {
+                compatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+            }
+            mDisplayAdjustments.setCompatibilityInfo(compatInfo);
+            mDisplayAdjustments.setActivityToken(activityToken);
+            mResources = mainThread.getTopLevelResources(mPackageInfo.getResDir(),
+                    Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
+        } else {
+            mDisplayAdjustments.setCompatibilityInfo(packageInfo.getCompatibilityInfo());
+            mDisplayAdjustments.setActivityToken(activityToken);
         }
         mMainThread = mainThread;
         mActivityToken = activityToken;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 2224490..573a6aa 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -40,7 +40,7 @@
 import android.os.UserHandle;
 import android.util.AndroidRuntimeException;
 import android.util.Slog;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
 import android.view.Display;
 
 import java.io.File;
@@ -84,7 +84,7 @@
     private final ClassLoader mBaseClassLoader;
     private final boolean mSecurityViolation;
     private final boolean mIncludeCode;
-    public final CompatibilityInfoHolder mCompatibilityInfo = new CompatibilityInfoHolder();
+    private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
     Resources mResources;
     private ClassLoader mClassLoader;
     private Application mApplication;
@@ -132,7 +132,7 @@
         mBaseClassLoader = baseLoader;
         mSecurityViolation = securityViolation;
         mIncludeCode = includeCode;
-        mCompatibilityInfo.set(compatInfo);
+        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
 
         if (mAppDir == null) {
             if (ActivityThread.mSystemContext == null) {
@@ -141,7 +141,7 @@
                 ActivityThread.mSystemContext.getResources().updateConfiguration(
                          mainThread.getConfiguration(),
                          mainThread.getDisplayMetricsLocked(
-                                 Display.DEFAULT_DISPLAY, compatInfo),
+                                 Display.DEFAULT_DISPLAY, mDisplayAdjustments),
                          compatInfo);
                 //Slog.i(TAG, "Created system resources "
                 //        + mSystemContext.getResources() + ": "
@@ -169,7 +169,7 @@
         mIncludeCode = true;
         mClassLoader = systemContext.getClassLoader();
         mResources = systemContext.getResources();
-        mCompatibilityInfo.set(compatInfo);
+        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
     }
 
     public String getPackageName() {
@@ -184,6 +184,14 @@
         return mSecurityViolation;
     }
 
+    public CompatibilityInfo getCompatibilityInfo() {
+        return mDisplayAdjustments.getCompatibilityInfo();
+    }
+
+    public void setCompatibilityInfo(CompatibilityInfo compatInfo) {
+        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
+    }
+
     /**
      * Gets the array of shared libraries that are listed as
      * used by the given package.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7c9117c..d8bf6ba 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -35,7 +35,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.AttributeSet;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
 import android.view.Display;
 import android.view.WindowManager;
 
@@ -2760,15 +2760,15 @@
     public abstract Context createDisplayContext(Display display);
 
     /**
-     * Gets the compatibility info holder for this context.  This information
-     * is provided on a per-application basis and is used to simulate lower density
-     * display metrics for legacy applications.
+     * Gets the display adjustments holder for this context.  This information
+     * is provided on a per-application or activity basis and is used to simulate lower density
+     * display metrics for legacy applications and restricted screen sizes.
      *
      * @param displayId The display id for which to get compatibility info.
      * @return The compatibility info holder, or null if not required by the application.
      * @hide
      */
-    public abstract CompatibilityInfoHolder getCompatibilityInfo(int displayId);
+    public abstract DisplayAdjustments getDisplayAdjustments(int displayId);
 
     /**
      * Indicates whether this Context is restricted.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 2f1bf8c..606a1f4 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -35,7 +35,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
 import android.view.Display;
 
 import java.io.File;
@@ -646,7 +646,7 @@
 
     /** @hide */
     @Override
-    public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
-        return mBase.getCompatibilityInfo(displayId);
+    public DisplayAdjustments getDisplayAdjustments(int displayId) {
+        return mBase.getDisplayAdjustments(displayId);
     }
 }
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 28c751c..da35ee9 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -471,8 +471,7 @@
      * Compute the frame Rect for applications runs under compatibility mode.
      *
      * @param dm the display metrics used to compute the frame size.
-     * @param orientation the orientation of the screen.
-     * @param outRect the output parameter which will contain the result.
+     * @param outDm If non-null the width and height will be set to their scaled values.
      * @return Returns the scaling factor for the window.
      */
     public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) {
@@ -518,6 +517,9 @@
 
     @Override
     public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
         try {
             CompatibilityInfo oc = (CompatibilityInfo)o;
             if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
@@ -579,10 +581,12 @@
 
     public static final Parcelable.Creator<CompatibilityInfo> CREATOR
             = new Parcelable.Creator<CompatibilityInfo>() {
+        @Override
         public CompatibilityInfo createFromParcel(Parcel source) {
             return new CompatibilityInfo(source);
         }
 
+        @Override
         public CompatibilityInfo[] newArray(int size) {
             return new CompatibilityInfo[size];
         }
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index cff974d..c24e0ee 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable.ConstantState;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -35,6 +36,7 @@
 import android.util.Slog;
 import android.util.TypedValue;
 import android.util.LongSparseArray;
+import android.view.DisplayAdjustments;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -117,8 +119,9 @@
     private final Configuration mConfiguration = new Configuration();
     /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
     private NativePluralRules mPluralRule;
-    
-    private CompatibilityInfo mCompatibilityInfo;
+
+    private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+    private WeakReference<IBinder> mToken;
 
     static {
         sPreloadedDrawables = new LongSparseArray[2];
@@ -173,7 +176,7 @@
      *               selecting/computing resource values (optional).
      */
     public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
-        this(assets, metrics, config, null);
+        this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
     }
 
     /**
@@ -184,15 +187,16 @@
      *                selecting/computing resource values.
      * @param config Desired device configuration to consider when 
      *               selecting/computing resource values (optional).
-     * @param compInfo this resource's compatibility info. It will use the default compatibility
-     *  info when it's null.
+     * @param compatInfo this resource's compatibility info. Must not be null.
+     * @param token The Activity token for determining stack affiliation. Usually null.
      * @hide
      */
-    public Resources(AssetManager assets, DisplayMetrics metrics,
-            Configuration config, CompatibilityInfo compInfo) {
+    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
+            CompatibilityInfo compatInfo, IBinder token) {
         mAssets = assets;
         mMetrics.setToDefaults();
-        mCompatibilityInfo = compInfo;
+        mCompatibilityInfo = compatInfo;
+        mToken = new WeakReference<IBinder>(token);
         updateConfiguration(config, metrics);
         assets.ensureStringBlocks();
     }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index dcf50cd..9e2e4ba 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -159,7 +159,7 @@
         Display display = mDisplays.get(displayId);
         if (display == null) {
             display = mGlobal.getCompatibleDisplay(displayId,
-                    mContext.getCompatibilityInfo(displayId));
+                    mContext.getDisplayAdjustments(displayId));
             if (display != null) {
                 mDisplays.put(displayId, display);
             }
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 3ab882d..320185d 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -28,7 +28,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
@@ -164,18 +164,18 @@
      * Gets information about a logical display.
      *
      * The display metrics may be adjusted to provide compatibility
-     * for legacy applications.
+     * for legacy applications or limited screen areas.
      *
      * @param displayId The logical display id.
-     * @param cih The compatibility info, or null if none is required.
+     * @param daj The compatibility info and activityToken.
      * @return The display object, or null if there is no display with the given id.
      */
-    public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) {
+    public Display getCompatibleDisplay(int displayId, DisplayAdjustments daj) {
         DisplayInfo displayInfo = getDisplayInfo(displayId);
         if (displayInfo == null) {
             return null;
         }
-        return new Display(this, displayId, displayInfo, cih);
+        return new Display(this, displayId, displayInfo, daj);
     }
 
     /**
@@ -185,7 +185,18 @@
      * @return The display object, or null if there is no display with the given id.
      */
     public Display getRealDisplay(int displayId) {
-        return getCompatibleDisplay(displayId, null);
+        return getCompatibleDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+    }
+
+    /**
+     * Gets information about a logical display without applying any compatibility metrics.
+     *
+     * @param displayId The logical display id.
+     * @param IBinder the activity token for this display.
+     * @return The display object, or null if there is no display with the given id.
+     */
+    public Display getRealDisplay(int displayId, IBinder token) {
+        return getCompatibleDisplay(displayId, new DisplayAdjustments(token));
     }
 
     public void registerDisplayListener(DisplayListener listener, Handler handler) {
diff --git a/core/java/android/print/IPrintAdapter.aidl b/core/java/android/print/IPrintAdapter.aidl
index a9b4fb7..f3ff8c4 100644
--- a/core/java/android/print/IPrintAdapter.aidl
+++ b/core/java/android/print/IPrintAdapter.aidl
@@ -17,7 +17,7 @@
 package android.print;
 
 import android.os.ParcelFileDescriptor;
-import android.print.IPrintProgressListener;
+import android.print.IPrintResultCallback;
 import android.print.PageRange;
 import android.print.PrintAttributes;
 
@@ -30,6 +30,6 @@
     void start();
     void printAttributesChanged(in PrintAttributes attributes);
     void print(in List<PageRange> pages, in ParcelFileDescriptor fd,
-            IPrintProgressListener progressListener);
+            IPrintResultCallback callback);
     void finish();
 }
diff --git a/core/java/android/print/IPrintProgressListener.aidl b/core/java/android/print/IPrintResultCallback.aidl
similarity index 81%
rename from core/java/android/print/IPrintProgressListener.aidl
rename to core/java/android/print/IPrintResultCallback.aidl
index 2c0d607..838377e 100644
--- a/core/java/android/print/IPrintProgressListener.aidl
+++ b/core/java/android/print/IPrintResultCallback.aidl
@@ -26,8 +26,8 @@
  *
  * @hide
  */
-oneway interface IPrintProgressListener {
-    void onWriteStarted(in PrintAdapterInfo info, ICancellationSignal cancellationSignal);
-    void onWriteFinished(in List<PageRange> pages);
-    void onWriteFailed(CharSequence error);
+oneway interface IPrintResultCallback {
+    void onPrintStarted(in PrintAdapterInfo info, ICancellationSignal cancellationSignal);
+    void onPrintFinished(in List<PageRange> pages);
+    void onPrintFailed(CharSequence error);
 }
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index 044a715..60e6229 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -21,7 +21,7 @@
 
 /**
  * Represents a range of pages. The start and end page indices of
- * the range are zero based and are inclusive.
+ * the range are zero based and inclusive.
  */
 public final class PageRange implements Parcelable {
 
diff --git a/core/java/android/print/PrintAdapter.java b/core/java/android/print/PrintAdapter.java
index a7f809b..6547c55 100644
--- a/core/java/android/print/PrintAdapter.java
+++ b/core/java/android/print/PrintAdapter.java
@@ -16,12 +16,11 @@
 
 package android.print;
 
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.util.List;
-
 import android.os.CancellationSignal;
 
+import java.io.FileDescriptor;
+import java.util.List;
+
 /**
  * Base class that provides data to be printed.
  *
@@ -33,19 +32,23 @@
  * This callback can be used to allocate resources.
  * </li>
  * <li>
- * Next you will get one or more calls to the pair
- *  {@link #onPrintAttributesChanged(PrintAttributes)} and {@link #onPrint(List,
- * FileDescriptor, CancellationSignal, PrintProgressCallback)}. The first callback
- * informs you that the print attributes (page size, density, etc) changed giving
- * you an opportunity to re-layout the content. The second method asks you to write
- * a PDF file with the content for specific pages.
+ * Next you will get one or more calls to {@link #onPrintAttributesChanged(
+ * PrintAttributes) to informs you that the print attributes (page size, density,
+ * etc) changed giving you an opportunity to re-layout the content.
+ * </li>
+ * <li>
+ * After every {@link #onPrintAttributesChanged(PrintAttributes) you will receive
+ * one or more calls to {@link #onPrint(List, FileDescriptor, CancellationSignal,
+ * PrintResultCallback)} asking you to write a PDF file with the content for
+ * specific pages.
  * </li>
  * <li>
  * Finally, you will receive a call on {@link #onFinish()} right after printing.
  * You can use this callback to release resources.
  * </li>
  * <li>
- * You can receive calls to {@link #getInfo()} at any point which should return
+ * You can receive calls to {@link #getInfo()} at any point after a call to
+ * {@link #onPrintAttributesChanged(PrintAttributes)} which should return
  * a {@link PrintAdapterInfo} describing your {@link PrintAdapter}.
  * </li>
  * </ul>
@@ -83,29 +86,28 @@
     /**
      * Called when specific pages of the content have to be printed in the from of
      * a PDF file to the given file descriptor. You should <strong>not</strong>
-     * close the file descriptor instead you have to invoke {@link PrintProgressCallback
-     * #onWriteFinished()} or {@link PrintProgressCallback#onPrintFailed(CharSequence)}.
+     * close the file descriptor instead you have to invoke {@link PrintResultCallback
+     * #onPrintFinished()} or {@link PrintResultCallback#onPrintFailed(CharSequence)}.
      * <p>
      * <strong>Note:</strong> If the printed content is large, it is a  good
      * practice to schedule writing it on a dedicated thread and register a
-     * callback in the provided {@link CancellationSignal} upon which to stop
-     * writing data. The cancellation callback will not be made on the main
-     * thread.
+     * callback in the provided {@link CancellationSignal} upon invocation of
+     * which you should stop writing data. The cancellation callback will not
+     * be made on the main thread.
      * </p>
      * <p>
      * <strong>Note:</strong> Invoked on the main thread.
      * </p>
-     * <p>
      *
-     * @param pages The pages whose content to write.
+     * @param pages The pages whose content to print.
      * @param destination The destination file descriptor to which to start writing.
-     * @param cancellationSignal Signal for observing cancel write requests.
+     * @param cancellationSignal Signal for observing cancel print requests.
      * @param progressListener Callback to inform the system with the write progress.
      *
      * @see CancellationSignal
      */
     public abstract void onPrint(List<PageRange> pages, FileDescriptor destination,
-            CancellationSignal cancellationSignal, PrintProgressCallback progressListener);
+            CancellationSignal cancellationSignal, PrintResultCallback progressListener);
 
     /**
      * Called when printing finished. You can use this callback to release
@@ -132,12 +134,12 @@
     public abstract PrintAdapterInfo getInfo();
 
     /**
-     * Base class for implementing a listener for the printing progress
+     * Base class for implementing a listener for the print result
      * of a {@link PrintAdapter}.
      */
-    public static abstract class PrintProgressCallback {
+    public static abstract class PrintResultCallback {
 
-        PrintProgressCallback() {
+        PrintResultCallback() {
             /* do nothing - hide constructor */
         }
 
diff --git a/core/java/android/print/PrintFileAdapter.java b/core/java/android/print/PrintFileAdapter.java
index bbfc394..dab9648 100644
--- a/core/java/android/print/PrintFileAdapter.java
+++ b/core/java/android/print/PrintFileAdapter.java
@@ -53,9 +53,9 @@
 
     @Override
     public void onPrint(List<PageRange> pages, FileDescriptor destination,
-            CancellationSignal cancellationSignal, PrintProgressCallback progressListener) {
+            CancellationSignal cancellationSignal, PrintResultCallback callback) {
         mWriteFileAsyncTask = new WriteFileAsyncTask(mFile, destination, cancellationSignal,
-                progressListener);
+                callback);
         mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
                 (Void[]) null);
         
@@ -73,15 +73,15 @@
 
         private final FileDescriptor mDestination;
 
-        private final PrintProgressCallback mProgressListener;
+        private final PrintResultCallback mResultCallback;
 
         private final CancellationSignal mCancellationSignal;
 
         public WriteFileAsyncTask(File source, FileDescriptor destination,
-                CancellationSignal cancellationSignal, PrintProgressCallback progressListener) {
+                CancellationSignal cancellationSignal, PrintResultCallback callback) {
             mSource = source;
             mDestination = destination;
-            mProgressListener = progressListener;
+            mResultCallback = callback;
             mCancellationSignal = cancellationSignal; 
             mCancellationSignal.setOnCancelListener(new OnCancelListener() {
                 @Override
@@ -113,9 +113,9 @@
                 if (!isCancelled()) {
                     List<PageRange> pages = new ArrayList<PageRange>();
                     pages.add(PageRange.ALL_PAGES);
-                    mProgressListener.onPrintFinished(pages);
+                    mResultCallback.onPrintFinished(pages);
                 } else {
-                    mProgressListener.onPrintFailed("Cancelled");
+                    mResultCallback.onPrintFailed("Cancelled");
                 }
             }
             return null;
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 32a0f5a..be9b596 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -26,7 +26,7 @@
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
-import android.print.PrintAdapter.PrintProgressCallback;
+import android.print.PrintAdapter.PrintResultCallback;
 import android.util.Log;
 
 import com.android.internal.os.SomeArgs;
@@ -162,6 +162,8 @@
      * @param pdfFile The PDF file to print.
      * @param attributes The default print job attributes.
      * @return The created print job.
+     *
+     * @see PrintJob
      */
     public PrintJob print(String printJobName, File pdfFile, PrintAttributes attributes) {
         PrintFileAdapter printable = new PrintFileAdapter(pdfFile);
@@ -176,6 +178,8 @@
      * @param printAdapter The printable adapter to print.
      * @param attributes The default print job attributes.
      * @return The created print job.
+     *
+     * @see PrintJob
      */
     public PrintJob print(String printJobName, PrintAdapter printAdapter,
             PrintAttributes attributes) {
@@ -252,7 +256,7 @@
 
         @Override
         public void print(List<PageRange> pages, ParcelFileDescriptor fd,
-                IPrintProgressListener progressListener) {
+                IPrintResultCallback callback) {
             synchronized (mLock) {
                 if (isFinishedLocked()) {
                     return;
@@ -261,7 +265,7 @@
                 args.arg1 = mPrintAdapter;
                 args.arg2 = pages;
                 args.arg3 = fd.getFileDescriptor();
-                args.arg4 = progressListener;
+                args.arg4 = callback;
                 mHandler.obtainMessage(MyHandler.MESSAGE_PRINT, args).sendToTarget();
             }
         }
@@ -318,16 +322,16 @@
                         @SuppressWarnings("unchecked")
                         List<PageRange> pages = (List<PageRange>) args.arg2;
                         final FileDescriptor fd = (FileDescriptor) args.arg3;
-                        IPrintProgressListener listener = (IPrintProgressListener) args.arg4;
+                        IPrintResultCallback callback = (IPrintResultCallback) args.arg4;
                         args.recycle();
                         try {
                             ICancellationSignal remoteSignal = CancellationSignal.createTransport();
-                            listener.onWriteStarted(adapter.getInfo(), remoteSignal);
+                            callback.onPrintStarted(adapter.getInfo(), remoteSignal);
 
                             CancellationSignal localSignal = CancellationSignal.fromTransport(
                                     remoteSignal);
                             adapter.onPrint(pages, fd, localSignal,
-                                    new PrintProgressListenerWrapper(listener) {
+                                    new PrintResultCallbackWrapper(callback) {
                                         @Override
                                         public void onPrintFinished(List<PageRange> pages) {
                                             IoUtils.closeQuietly(fd);
@@ -363,29 +367,29 @@
         }
     }
 
-    private static abstract class PrintProgressListenerWrapper extends PrintProgressCallback {
+    private static abstract class PrintResultCallbackWrapper extends PrintResultCallback {
 
-        private final IPrintProgressListener mWrappedListener;
+        private final IPrintResultCallback mWrappedCallback;
 
-        public PrintProgressListenerWrapper(IPrintProgressListener listener) {
-            mWrappedListener = listener;
+        public PrintResultCallbackWrapper(IPrintResultCallback callback) {
+            mWrappedCallback = callback;
         }
 
         @Override
         public void onPrintFinished(List<PageRange> pages) {
             try {
-                mWrappedListener.onWriteFinished(pages);
+                mWrappedCallback.onPrintFinished(pages);
             } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error calling onWriteFinished", re);
+                Log.e(LOG_TAG, "Error calling onPrintFinished", re);
             }
         }
 
         @Override
         public void onPrintFailed(CharSequence error) {
             try {
-                mWrappedListener.onWriteFailed(error);
+                mWrappedCallback.onPrintFailed(error);
             } catch (RemoteException re) {
-                Log.e(LOG_TAG, "Error calling onWriteFailed", re);
+                Log.e(LOG_TAG, "Error calling onPrintFailed", re);
             }
         }
     }
diff --git a/core/java/android/print/pdf/PdfDocument.java b/core/java/android/print/pdf/PdfDocument.java
index 7fb170e..7ce036d 100644
--- a/core/java/android/print/pdf/PdfDocument.java
+++ b/core/java/android/print/pdf/PdfDocument.java
@@ -105,7 +105,7 @@
      * is created you can draw arbitrary content on the page's canvas which
      * you can get by calling {@link Page#getCanvas()}. After you are done
      * drawing the content you should finish the page by calling
-     * {@link #finishPage(Page). After the page is finished you should
+     * {@link #finishPage(Page)}. After the page is finished you should
      * no longer access the page or its canvas.
      * <p>
      * <strong>Note:</strong> Do not call this method after {@link #close()}.
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 9688761..f490f91 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -62,7 +62,7 @@
      * <p>
      * <strong>Node:</strong>The returned info object is a snapshot of the
      * current print job state. Every call to this method returns a fresh
-     * info object that reflects the current print jobs state.
+     * info object that reflects the current print job state.
      * </p>
      *
      * @return The print job info.
@@ -100,7 +100,7 @@
      *
      * @see #complete()
      * @see #cancel()
-     * @see #fail()
+     * @see #fail(CharSequence)
      */
     public boolean isStarted() {
         return  getInfo().getState() == PrintJobInfo.STATE_STARTED;
@@ -140,7 +140,8 @@
      * Fails the print job. You should call this method if {@link
      * #isStarted()} returns true you filed while printing.
      *
-     * @return Whether the job as failed.
+     * @param error The reason for the failure.
+     * @return Whether the job was failed.
      *
      * @see #isStarted()
      */
@@ -191,6 +192,9 @@
      * Gets the data associated with this print job. It is a responsibility of
      * the print service to open a stream to the returned file descriptor
      * and fully read the content.
+     * <p>
+     * <strong>Note:</strong> It is your responsibility to close the file descriptor.
+     * </p>
      *
      * @return A file descriptor for reading the data or <code>null</code>.
      */
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index d5cadc0..9256966 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -52,7 +52,7 @@
  * Calls to {@link #addDiscoveredPrinters(List)} and
  * {@link #removeDiscoveredPrinters(List)} before a call to
  * {@link #onStartPrinterDiscovery()} and after a call to
- * {@link #onStopPrinterDiscovery()} is a no-op.
+ * {@link #onStopPrinterDiscovery()} are a no-op.
  * </p>
  * <p>
  * For every printer discovery period all printers have to be added. Each
@@ -68,7 +68,7 @@
  * service may handle it immediately or schedule that for an appropriate
  * time in the future. The list of all print jobs for this service
  * are be available by calling {@link #getPrintJobs()}. A queued print
- * job is in a {@link PrintJobInfo#STATE_QUEUED} state.
+ * job is one whose {@link PrintJob#isQueued()} return true.
  * </p>
  * <p>
  * A print service is responsible for setting the print job state as
@@ -200,7 +200,7 @@
     /**
      * Callback requesting from this service to start printer discovery.
      * At the end of the printer discovery period the system will call
-     * {@link #onStopPrinterDiscovery(). Discovered printers should be
+     * {@link #onStopPrinterDiscovery()}. Discovered printers should be
      * reported by calling #addDiscoveredPrinters(List) and reported ones
      * that disappear should be reported by calling
      * {@link #removeDiscoveredPrinters(List)}.
@@ -299,8 +299,7 @@
     /**
      * Called when canceling of a print job is requested. The service
      * should do best effort to fulfill the request. After the print
-     * job is canceled it state has to be set to
-     * {@link PrintJobInfo#STATE_CANCELED}.
+     * job is canceled by calling {@link PrintJob#cancel()}.
      *
      * @param printJob The print job to be canceled.
      */
diff --git a/core/java/android/view/CompatibilityInfoHolder.java b/core/java/android/view/CompatibilityInfoHolder.java
deleted file mode 100644
index fc8d684..0000000
--- a/core/java/android/view/CompatibilityInfoHolder.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.view;
-
-import android.content.res.CompatibilityInfo;
-
-/** @hide */
-public class CompatibilityInfoHolder {
-    private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
-
-    public void set(CompatibilityInfo compatInfo) {
-        if (compatInfo != null && (compatInfo.isScalingRequired()
-                || !compatInfo.supportsScreen())) {
-            mCompatInfo = compatInfo;
-        } else {
-            mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
-        }
-    }
-
-    public CompatibilityInfo get() {
-        return mCompatInfo;
-    }
-
-    public CompatibilityInfo getIfNeeded() {
-        CompatibilityInfo ci = mCompatInfo;
-        if (ci == null || ci  == CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO) {
-            return null;
-        }
-        return ci;
-    }
-}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4d984fd..0e34435 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.content.res.CompatibilityInfo;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -60,7 +61,7 @@
     private final String mAddress;
     private final int mOwnerUid;
     private final String mOwnerPackageName;
-    private final CompatibilityInfoHolder mCompatibilityInfo;
+    private final DisplayAdjustments mDisplayAdjustments;
 
     private DisplayInfo mDisplayInfo; // never null
     private boolean mIsValid;
@@ -203,11 +204,11 @@
      */
     public Display(DisplayManagerGlobal global,
             int displayId, DisplayInfo displayInfo /*not null*/,
-            CompatibilityInfoHolder compatibilityInfo) {
+            DisplayAdjustments daj) {
         mGlobal = global;
         mDisplayId = displayId;
         mDisplayInfo = displayInfo;
-        mCompatibilityInfo = compatibilityInfo;
+        mDisplayAdjustments = daj;
         mIsValid = true;
 
         // Cache properties that cannot change as long as the display is valid.
@@ -348,11 +349,11 @@
     /**
      * Gets the compatibility info used by this display instance.
      *
-     * @return The compatibility info holder, or null if none is required.
+     * @return The display adjustments holder, or null if none is required.
      * @hide
      */
-    public CompatibilityInfoHolder getCompatibilityInfo() {
-        return mCompatibilityInfo;
+    public DisplayAdjustments getDisplayAdjustments() {
+        return mDisplayAdjustments;
     }
 
     /**
@@ -393,7 +394,7 @@
     public void getSize(Point outSize) {
         synchronized (this) {
             updateDisplayInfoLocked();
-            mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+            mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments);
             outSize.x = mTempMetrics.widthPixels;
             outSize.y = mTempMetrics.heightPixels;
         }
@@ -408,7 +409,7 @@
     public void getRectSize(Rect outSize) {
         synchronized (this) {
             updateDisplayInfoLocked();
-            mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+            mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments);
             outSize.set(0, 0, mTempMetrics.widthPixels, mTempMetrics.heightPixels);
         }
     }
@@ -573,7 +574,7 @@
     public void getMetrics(DisplayMetrics outMetrics) {
         synchronized (this) {
             updateDisplayInfoLocked();
-            mDisplayInfo.getAppMetrics(outMetrics, mCompatibilityInfo);
+            mDisplayInfo.getAppMetrics(outMetrics, mDisplayAdjustments);
         }
     }
 
@@ -611,7 +612,9 @@
     public void getRealMetrics(DisplayMetrics outMetrics) {
         synchronized (this) {
             updateDisplayInfoLocked();
-            mDisplayInfo.getLogicalMetrics(outMetrics, null);
+            mDisplayInfo.getLogicalMetrics(outMetrics,
+                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,
+                    mDisplayAdjustments.getActivityToken());
         }
     }
 
@@ -658,7 +661,7 @@
         long now = SystemClock.uptimeMillis();
         if (now > mLastCachedAppSizeUpdate + CACHED_APP_SIZE_DURATION_MILLIS) {
             updateDisplayInfoLocked();
-            mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+            mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments);
             mCachedAppWidthCompat = mTempMetrics.widthPixels;
             mCachedAppHeightCompat = mTempMetrics.heightPixels;
             mLastCachedAppSizeUpdate = now;
@@ -670,7 +673,7 @@
     public String toString() {
         synchronized (this) {
             updateDisplayInfoLocked();
-            mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+            mDisplayInfo.getAppMetrics(mTempMetrics, mDisplayAdjustments);
             return "Display id " + mDisplayId + ": " + mDisplayInfo
                     + ", " + mTempMetrics + ", isValid=" + mIsValid;
         }
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
new file mode 100644
index 0000000..4a234ad
--- /dev/null
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -0,0 +1,93 @@
+/*
+ * 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.view;
+
+import android.content.res.CompatibilityInfo;
+import android.os.IBinder;
+
+import com.android.internal.util.Objects;
+
+/** @hide */
+public class DisplayAdjustments {
+    public static final boolean DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN = false;
+
+    public static final DisplayAdjustments DEFAULT_DISPLAY_ADJUSTMENTS = new DisplayAdjustments();
+
+    private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+    private volatile IBinder mActivityToken;
+
+    public DisplayAdjustments() {
+    }
+
+    public DisplayAdjustments(IBinder token) {
+        mActivityToken = token;
+    }
+
+    public DisplayAdjustments(CompatibilityInfo compatInfo, IBinder token) {
+        setCompatibilityInfo(compatInfo);
+        mActivityToken = token;
+    }
+
+    public void setCompatibilityInfo(CompatibilityInfo compatInfo) {
+        if (this == DEFAULT_DISPLAY_ADJUSTMENTS) {
+            throw new IllegalArgumentException(
+                    "setCompatbilityInfo: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
+        }
+        if (compatInfo != null && (compatInfo.isScalingRequired()
+                || !compatInfo.supportsScreen())) {
+            mCompatInfo = compatInfo;
+        } else {
+            mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+        }
+    }
+
+    public CompatibilityInfo getCompatibilityInfo() {
+        return mCompatInfo;
+    }
+
+    public void setActivityToken(IBinder token) {
+        if (this == DEFAULT_DISPLAY_ADJUSTMENTS) {
+            throw new IllegalArgumentException(
+                    "setActivityToken: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
+        }
+        mActivityToken = token;
+    }
+
+    public IBinder getActivityToken() {
+        return mActivityToken;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 17;
+        hash = hash * 31 + mCompatInfo.hashCode();
+        if (DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN) {
+            hash = hash * 31 + (mActivityToken == null ? 0 : mActivityToken.hashCode());
+        }
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof DisplayAdjustments)) {
+            return false;
+        }
+        DisplayAdjustments daj = (DisplayAdjustments)o;
+        return Objects.equal(daj.mCompatInfo, mCompatInfo) &&
+                Objects.equal(daj.mActivityToken, mActivityToken);
+    }
+}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 1442cb7..9a9c4cd 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.content.res.CompatibilityInfo;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
@@ -343,12 +344,22 @@
         return 0;
     }
 
-    public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) {
-        getMetricsWithSize(outMetrics, cih, appWidth, appHeight);
+    public void getAppMetrics(DisplayMetrics outMetrics) {
+        getAppMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
     }
 
-    public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) {
-        getMetricsWithSize(outMetrics, cih, logicalWidth, logicalHeight);
+    public void getAppMetrics(DisplayMetrics outMetrics, DisplayAdjustments displayAdjustments) {
+        getMetricsWithSize(outMetrics, displayAdjustments.getCompatibilityInfo(),
+                displayAdjustments.getActivityToken(), appWidth, appHeight);
+    }
+
+    public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo ci, IBinder token) {
+        getMetricsWithSize(outMetrics, ci, token, appWidth, appHeight);
+    }
+
+    public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+            IBinder token) {
+        getMetricsWithSize(outMetrics, compatInfo, token, logicalWidth, logicalHeight);
     }
 
     public int getNaturalWidth() {
@@ -368,8 +379,8 @@
         return Display.hasAccess(uid, flags, ownerUid);
     }
 
-    private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfoHolder cih,
-            int width, int height) {
+    private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
+            IBinder token, int width, int height) {
         outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi;
         outMetrics.noncompatWidthPixels  = outMetrics.widthPixels = width;
         outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
@@ -380,11 +391,8 @@
         outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi;
         outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi;
 
-        if (cih != null) {
-            CompatibilityInfo ci = cih.getIfNeeded();
-            if (ci != null) {
-                ci.applyToDisplayMetrics(outMetrics);
-            }
+        if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
+            compatInfo.applyToDisplayMetrics(outMetrics);
         }
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d3f9174..a40582b 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4426,8 +4426,10 @@
     /**
      * Quick invalidation method that simply transforms the dirty rect into the parent's
      * coordinate system, pruning the invalidation if the parent has already been invalidated.
+     *
+     * @hide
      */
-    private ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) {
+    protected ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) {
         if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
                 (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
             dirty.offset(left - mScrollX, top - mScrollY);
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
index 2d86bfe..975931a 100644
--- a/core/java/android/view/ViewOverlay.java
+++ b/core/java/android/view/ViewOverlay.java
@@ -300,6 +300,17 @@
             }
         }
 
+        /**
+         * @hide
+         */
+        @Override
+        protected ViewParent invalidateChildInParentFast(int left, int top, Rect dirty) {
+            if (mHostView instanceof ViewGroup) {
+                return ((ViewGroup) mHostView).invalidateChildInParentFast(left, top, dirty);
+            }
+            return null;
+        }
+
         @Override
         public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
             if (mHostView != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9bc66be..93c6d6e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -239,7 +239,7 @@
     boolean mAdded;
     boolean mAddedTouchMode;
 
-    final CompatibilityInfoHolder mCompatibilityInfo;
+    final DisplayAdjustments mDisplayAdjustments;
 
     // These are accessed by multiple threads.
     final Rect mWinFrame; // frame given by window manager.
@@ -336,8 +336,7 @@
         mDisplay = display;
         mBasePackageName = context.getBasePackageName();
 
-        CompatibilityInfoHolder cih = display.getCompatibilityInfo();
-        mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder();
+        mDisplayAdjustments = display.getDisplayAdjustments();
 
         mThread = Thread.currentThread();
         mLocation = new WindowLeaked(null);
@@ -444,8 +443,9 @@
                     }
                 }
 
-                CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
+                CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo();
                 mTranslator = compatibilityInfo.getTranslator();
+                mDisplayAdjustments.setActivityToken(attrs.token);
 
                 // If the application owns the surface, don't enable hardware acceleration
                 if (mSurfaceHolder == null) {
@@ -1136,7 +1136,7 @@
             surfaceChanged = true;
             params = lp;
         }
-        CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
+        CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo();
         if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
             params = lp;
             mFullRedrawNeeded = true;
@@ -2847,8 +2847,8 @@
                 + mWindowAttributes.getTitle()
                 + ": " + config);
 
-        CompatibilityInfo ci = mCompatibilityInfo.getIfNeeded();
-        if (ci != null) {
+        CompatibilityInfo ci = mDisplayAdjustments.getCompatibilityInfo();
+        if (!ci.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
             config = new Configuration(config);
             ci.applyToConfiguration(mNoncompatDensity, config);
         }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1991af1..bb1f954 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1280,12 +1280,12 @@
     }
 
     /**
-     * If fast scroll is visible, then don't draw the vertical scrollbar.
+     * If fast scroll is enabled, then don't draw the vertical scrollbar.
      * @hide
      */
     @Override
     protected boolean isVerticalScrollBarHidden() {
-        return mFastScroller != null && mFastScroller.isVisible();
+        return mFastScrollEnabled;
     }
 
     /**
@@ -1337,7 +1337,7 @@
      */
     void invokeOnItemScrollListener() {
         if (mFastScroller != null) {
-            mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
+            mFastScroller.onScroll(mFirstPosition, getChildCount(), mItemCount);
         }
         if (mOnScrollListener != null) {
             mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
@@ -2009,7 +2009,7 @@
             }
             mRecycler.markChildrenDirty();
         }
-        
+
         if (mFastScroller != null && mItemCount != mOldItemCount) {
             mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
         }
@@ -3752,18 +3752,6 @@
                 canvas.restoreToCount(restoreCount);
             }
         }
-        if (mFastScroller != null) {
-            final int scrollY = mScrollY;
-            if (scrollY != 0) {
-                // Pin to the top/bottom during overscroll
-                int restoreCount = canvas.save();
-                canvas.translate(0, scrollY);
-                mFastScroller.draw(canvas);
-                canvas.restoreToCount(restoreCount);
-            } else {
-                mFastScroller.draw(canvas);
-            }
-        }
     }
 
     /**
@@ -3820,11 +3808,8 @@
             return false;
         }
 
-        if (mFastScroller != null) {
-            boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
-            if (intercepted) {
-                return true;
-            }
+        if (mFastScroller != null && mFastScroller.onInterceptTouchEvent(ev)) {
+            return true;
         }
 
         switch (action & MotionEvent.ACTION_MASK) {
@@ -5672,78 +5657,96 @@
             return mDefInputConnection.sendKeyEvent(event);
         }
 
+        @Override
         public CharSequence getTextBeforeCursor(int n, int flags) {
             if (mTarget == null) return "";
             return mTarget.getTextBeforeCursor(n, flags);
         }
 
+        @Override
         public CharSequence getTextAfterCursor(int n, int flags) {
             if (mTarget == null) return "";
             return mTarget.getTextAfterCursor(n, flags);
         }
 
+        @Override
         public CharSequence getSelectedText(int flags) {
             if (mTarget == null) return "";
             return mTarget.getSelectedText(flags);
         }
 
+        @Override
         public int getCursorCapsMode(int reqModes) {
             if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
             return mTarget.getCursorCapsMode(reqModes);
         }
 
+        @Override
         public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
             return getTarget().getExtractedText(request, flags);
         }
 
+        @Override
         public boolean deleteSurroundingText(int beforeLength, int afterLength) {
             return getTarget().deleteSurroundingText(beforeLength, afterLength);
         }
 
+        @Override
         public boolean setComposingText(CharSequence text, int newCursorPosition) {
             return getTarget().setComposingText(text, newCursorPosition);
         }
 
+        @Override
         public boolean setComposingRegion(int start, int end) {
             return getTarget().setComposingRegion(start, end);
         }
 
+        @Override
         public boolean finishComposingText() {
             return mTarget == null || mTarget.finishComposingText();
         }
 
+        @Override
         public boolean commitText(CharSequence text, int newCursorPosition) {
             return getTarget().commitText(text, newCursorPosition);
         }
 
+        @Override
         public boolean commitCompletion(CompletionInfo text) {
             return getTarget().commitCompletion(text);
         }
 
+        @Override
         public boolean commitCorrection(CorrectionInfo correctionInfo) {
             return getTarget().commitCorrection(correctionInfo);
         }
 
+        @Override
         public boolean setSelection(int start, int end) {
             return getTarget().setSelection(start, end);
         }
 
+        @Override
         public boolean performContextMenuAction(int id) {
             return getTarget().performContextMenuAction(id);
         }
 
+        @Override
         public boolean beginBatchEdit() {
             return getTarget().beginBatchEdit();
         }
 
+        @Override
         public boolean endBatchEdit() {
             return getTarget().endBatchEdit();
         }
 
+        @Override
         public boolean clearMetaKeyStates(int states) {
             return getTarget().clearMetaKeyStates(states);
         }
 
+        @Override
         public boolean performPrivateCommand(String action, Bundle data) {
             return getTarget().performPrivateCommand(action, data);
         }
@@ -6037,9 +6040,9 @@
 
     /**
      * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
-     * 
+     *
      * @param handler The OnClickHandler to use when inflating RemoteViews.
-     * 
+     *
      * @hide
      */
     public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index aa33384..62e15786 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -16,49 +16,66 @@
 
 package android.widget;
 
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.NinePatchDrawable;
-import android.os.Handler;
-import android.os.SystemClock;
+import android.os.Build;
+import android.text.TextUtils.TruncateAt;
+import android.util.IntProperty;
+import android.util.MathUtils;
+import android.util.Property;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.MeasureSpec;
 import android.view.ViewConfiguration;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroupOverlay;
 import android.widget.AbsListView.OnScrollListener;
 
+import com.android.internal.R;
+
 /**
  * Helper class for AbsListView to draw and control the Fast Scroll thumb
  */
 class FastScroller {
-    private static final String TAG = "FastScroller";
+    /** Duration of fade-out animation. */
+    private static final int DURATION_FADE_OUT = 300;
 
-    // Minimum number of pages to justify showing a fast scroll thumb
-    private static int MIN_PAGES = 4;
-    // Scroll thumb not showing
+    /** Duration of fade-in animation. */
+    private static final int DURATION_FADE_IN = 150;
+
+    /** Duration of transition cross-fade animation. */
+    private static final int DURATION_CROSS_FADE = 50;
+
+    /** Duration of transition resize animation. */
+    private static final int DURATION_RESIZE = 100;
+
+    /** Inactivity timeout before fading controls. */
+    private static final long FADE_TIMEOUT = 1500;
+
+    /** Minimum number of pages to justify showing a fast scroll thumb. */
+    private static final int MIN_PAGES = 4;
+
+    /** Scroll thumb and preview not showing. */
     private static final int STATE_NONE = 0;
-    // Not implemented yet - fade-in transition
-    @SuppressWarnings("unused")
-    private static final int STATE_ENTER = 1;
-    // Scroll thumb visible and moving along with the scrollbar
-    private static final int STATE_VISIBLE = 2;
-    // Scroll thumb being dragged by user
-    private static final int STATE_DRAGGING = 3;
-    // Scroll thumb fading out due to inactivity timeout
-    private static final int STATE_EXIT = 4;
 
-    private static final int[] PRESSED_STATES = new int[] {
-        android.R.attr.state_pressed
-    };
+    /** Scroll thumb visible and moving along with the scrollbar. */
+    private static final int STATE_VISIBLE = 1;
 
-    private static final int[] DEFAULT_STATES = new int[0];
+    /** Scroll thumb and preview being dragged by user. */
+    private static final int STATE_DRAGGING = 2;
 
+    /** Styleable attributes. */
     private static final int[] ATTRS = new int[] {
         android.R.attr.fastScrollTextColor,
         android.R.attr.fastScrollThumbDrawable,
@@ -68,6 +85,7 @@
         android.R.attr.fastScrollOverlayPosition
     };
 
+    // Styleable attribute indices.
     private static final int TEXT_COLOR = 0;
     private static final int THUMB_DRAWABLE = 1;
     private static final int TRACK_DRAWABLE = 2;
@@ -75,113 +93,247 @@
     private static final int PREVIEW_BACKGROUND_RIGHT = 4;
     private static final int OVERLAY_POSITION = 5;
 
+    // Positions for preview image and text.
     private static final int OVERLAY_FLOATING = 0;
     private static final int OVERLAY_AT_THUMB = 1;
 
-    private Drawable mThumbDrawable;
-    private Drawable mOverlayDrawable;
-    private Drawable mTrackDrawable;
+    // Indices for mPreviewResId.
+    private static final int PREVIEW_LEFT = 0;
+    private static final int PREVIEW_RIGHT = 1;
 
-    private Drawable mOverlayDrawableLeft;
-    private Drawable mOverlayDrawableRight;
+    /** Delay before considering a tap in the thumb area to be a drag. */
+    private static final long TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
 
-    int mThumbH;
-    int mThumbW;
-    int mThumbY;
+    private final Rect mTempBounds = new Rect();
+    private final Rect mTempMargins = new Rect();
 
-    private RectF mOverlayPos;
-    private int mOverlaySize;
-    private int mOverlayPadding;
+    private final AbsListView mList;
+    private final ViewGroupOverlay mOverlay;
+    private final TextView mPrimaryText;
+    private final TextView mSecondaryText;
+    private final ImageView mThumbImage;
+    private final ImageView mTrackImage;
+    private final ImageView mPreviewImage;
 
-    AbsListView mList;
-    boolean mScrollCompleted;
-    private int mVisibleItem;
-    private Paint mPaint;
-    private int mListOffset;
+    /**
+     * Preview image resource IDs for left- and right-aligned layouts. See
+     * {@link #PREVIEW_LEFT} and {@link #PREVIEW_RIGHT}.
+     */
+    private final int[] mPreviewResId = new int[2];
+
+    /**
+     * Padding in pixels around the preview text. Applied as layout margins to
+     * the preview text and padding to the preview image.
+     */
+    private final int mPreviewPadding;
+
+    /** Whether there is a track image to display. */
+    private final boolean mHasTrackImage;
+
+    /** Set containing decoration transition animations. */
+    private AnimatorSet mDecorAnimation;
+
+    /** Set containing preview text transition animations. */
+    private AnimatorSet mPreviewAnimation;
+
+    /** Whether the primary text is showing. */
+    private boolean mShowingPrimary;
+
+    /** Whether we're waiting for completion of scrollTo(). */
+    private boolean mScrollCompleted;
+
+    /** The position of the first visible item in the list. */
+    private int mFirstVisibleItem;
+
+    /** The number of headers at the top of the view. */
+    private int mHeaderCount;
+
+    /** The number of items in the list. */
     private int mItemCount = -1;
+
+    /** The index of the current section. */
+    private int mCurrentSection = -1;
+
+    /** Whether the list is long enough to need a fast scroller. */
     private boolean mLongList;
 
-    private Object [] mSections;
-    private String mSectionText;
-    private boolean mDrawOverlay;
-    private ScrollFade mScrollFade;
+    private Object[] mSections;
 
+    /**
+     * Current decoration state, one of:
+     * <ul>
+     * <li>{@link #STATE_NONE}, nothing visible
+     * <li>{@link #STATE_VISIBLE}, showing track and thumb
+     * <li>{@link #STATE_DRAGGING}, visible and showing preview
+     * </ul>
+     */
     private int mState;
 
-    private Handler mHandler = new Handler();
-
-    BaseAdapter mListAdapter;
+    private BaseAdapter mListAdapter;
     private SectionIndexer mSectionIndexer;
 
-    private boolean mChangedBounds;
+    /** Whether decorations should be laid out from right to left. */
+    private boolean mLayoutFromRight;
 
-    private int mPosition;
-
+    /** Whether the scrollbar and decorations should always be shown. */
     private boolean mAlwaysShow;
 
+    /**
+     * Position for the preview image and text. One of:
+     * <ul>
+     * <li>{@link #OVERLAY_AT_THUMB}
+     * <li>{@link #OVERLAY_FLOATING}
+     * </ul>
+     */
     private int mOverlayPosition;
 
+    /** Whether to precisely match the thumb position to the list. */
     private boolean mMatchDragPosition;
 
-    float mInitialTouchY;
-    boolean mPendingDrag;
+    private float mInitialTouchY;
+    private boolean mHasPendingDrag;
     private int mScaledTouchSlop;
 
-    private static final int FADE_TIMEOUT = 1500;
-    private static final int PENDING_DRAG_DELAY = 180;
-
-    private final Rect mTmpRect = new Rect();
-
     private final Runnable mDeferStartDrag = new Runnable() {
         @Override
         public void run() {
             if (mList.mIsAttached) {
                 beginDrag();
 
-                final int viewHeight = mList.getHeight();
-                // Jitter
-                int newThumbY = (int) mInitialTouchY - mThumbH + 10;
-                if (newThumbY < 0) {
-                    newThumbY = 0;
-                } else if (newThumbY + mThumbH > viewHeight) {
-                    newThumbY = viewHeight - mThumbH;
-                }
-                mThumbY = newThumbY;
-                scrollTo((float) mThumbY / (viewHeight - mThumbH));
+                final float pos = getPosFromMotionEvent(mInitialTouchY);
+                scrollTo(pos);
             }
 
-            mPendingDrag = false;
+            mHasPendingDrag = false;
+        }
+    };
+
+    /**
+     * Used to delay hiding fast scroll decorations.
+     */
+    private final Runnable mDeferHide = new Runnable() {
+        @Override
+        public void run() {
+            setState(STATE_NONE);
+        }
+    };
+
+    /**
+     * Used to effect a transition from primary to secondary text.
+     */
+    private final AnimatorListener mSwitchPrimaryListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mShowingPrimary = !mShowingPrimary;
         }
     };
 
     public FastScroller(Context context, AbsListView listView) {
         mList = listView;
-        init(context);
+        mOverlay = listView.getOverlay();
+
+        mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+
+        final Resources res = context.getResources();
+        final TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS);
+
+        mTrackImage = new ImageView(context);
+
+        // Add track to overlay if it has an image.
+        final int trackResId = ta.getResourceId(TRACK_DRAWABLE, 0);
+        if (trackResId != 0) {
+            mHasTrackImage = true;
+            mTrackImage.setBackgroundResource(trackResId);
+            mOverlay.add(mTrackImage);
+        } else {
+            mHasTrackImage = false;
+        }
+
+        mThumbImage = new ImageView(context);
+
+        // Add thumb to overlay if it has an image.
+        final Drawable thumbDrawable = ta.getDrawable(THUMB_DRAWABLE);
+        if (thumbDrawable != null) {
+            mThumbImage.setImageDrawable(thumbDrawable);
+            mOverlay.add(mThumbImage);
+        }
+
+        // If necessary, apply minimum thumb width and height.
+        if (thumbDrawable.getIntrinsicWidth() <= 0 || thumbDrawable.getIntrinsicHeight() <= 0) {
+            mThumbImage.setMinimumWidth(res.getDimensionPixelSize(R.dimen.fastscroll_thumb_width));
+            mThumbImage.setMinimumHeight(
+                    res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height));
+        }
+
+        final int previewSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size);
+        mPreviewImage = new ImageView(context);
+        mPreviewImage.setMinimumWidth(previewSize);
+        mPreviewImage.setMinimumHeight(previewSize);
+        mPreviewImage.setAlpha(0f);
+        mOverlay.add(mPreviewImage);
+
+        mPreviewPadding = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_padding);
+
+        mPrimaryText = createPreviewTextView(context, ta);
+        mOverlay.add(mPrimaryText);
+        mSecondaryText = createPreviewTextView(context, ta);
+        mOverlay.add(mSecondaryText);
+
+        mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(PREVIEW_BACKGROUND_LEFT, 0);
+        mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(PREVIEW_BACKGROUND_RIGHT, 0);
+        mOverlayPosition = ta.getInt(OVERLAY_POSITION, OVERLAY_FLOATING);
+        ta.recycle();
+
+        mScrollCompleted = true;
+        mState = STATE_VISIBLE;
+        mMatchDragPosition =
+                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
+
+        getSectionsFromIndexer();
+        refreshDrawablePressedState();
+        setScrollbarPosition(mList.getVerticalScrollbarPosition());
+
+        mList.postDelayed(mDeferHide, FADE_TIMEOUT);
     }
 
+    /**
+     * @param alwaysShow Whether the fast scroll thumb should always be shown
+     */
     public void setAlwaysShow(boolean alwaysShow) {
         mAlwaysShow = alwaysShow;
+
         if (alwaysShow) {
-            mHandler.removeCallbacks(mScrollFade);
             setState(STATE_VISIBLE);
         } else if (mState == STATE_VISIBLE) {
-            mHandler.postDelayed(mScrollFade, FADE_TIMEOUT);
+            mList.postDelayed(mDeferHide, FADE_TIMEOUT);
         }
     }
 
+    /**
+     * @return Whether the fast scroll thumb will always be shown
+     * @see #setAlwaysShow(boolean)
+     */
     public boolean isAlwaysShowEnabled() {
         return mAlwaysShow;
     }
 
-    private void refreshDrawableState() {
-        int[] state = mState == STATE_DRAGGING ? PRESSED_STATES : DEFAULT_STATES;
+    /**
+     * Immediately transitions the fast scroller decorations to a hidden state.
+     */
+    public void stop() {
+        setState(STATE_NONE);
+    }
 
-        if (mThumbDrawable != null && mThumbDrawable.isStateful()) {
-            mThumbDrawable.setState(state);
+    /**
+     * @return Whether the fast scroll thumb should be shown.
+     */
+    public boolean shouldShow() {
+        // Don't show if the list is as tall as or shorter than the thumbnail.
+        if (mList.getHeight() <= mThumbImage.getHeight()) {
+            return false;
         }
-        if (mTrackDrawable != null && mTrackDrawable.isStateful()) {
-            mTrackDrawable.setState(state);
-        }
+
+        return true;
     }
 
     public void setScrollbarPosition(int position) {
@@ -189,374 +341,403 @@
             position = mList.isLayoutRtl() ?
                     View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
         }
-        mPosition = position;
-        switch (position) {
-            default:
-            case View.SCROLLBAR_POSITION_RIGHT:
-                mOverlayDrawable = mOverlayDrawableRight;
-                break;
-            case View.SCROLLBAR_POSITION_LEFT:
-                mOverlayDrawable = mOverlayDrawableLeft;
-                break;
+
+        mLayoutFromRight = position != View.SCROLLBAR_POSITION_LEFT;
+
+        final int previewResId = mPreviewResId[mLayoutFromRight ? PREVIEW_RIGHT : PREVIEW_LEFT];
+        mPreviewImage.setBackgroundResource(previewResId);
+
+        // Add extra padding for text.
+        final Drawable background = mPreviewImage.getBackground();
+        if (background != null) {
+            final Rect padding = mTempBounds;
+            background.getPadding(padding);
+            padding.offset(mPreviewPadding, mPreviewPadding);
+            mPreviewImage.setPadding(padding.left, padding.top, padding.right, padding.bottom);
         }
+
+        updateLayout();
     }
 
     public int getWidth() {
-        return mThumbW;
+        return mThumbImage.getWidth();
     }
 
-    public void setState(int state) {
-        switch (state) {
-            case STATE_NONE:
-                mHandler.removeCallbacks(mScrollFade);
-                mList.invalidate();
-                break;
-            case STATE_VISIBLE:
-                if (mState != STATE_VISIBLE) { // Optimization
-                    resetThumbPos();
-                }
-                // Fall through
-            case STATE_DRAGGING:
-                mHandler.removeCallbacks(mScrollFade);
-                break;
-            case STATE_EXIT:
-                final int viewWidth = mList.getWidth();
-                final int top = mThumbY;
-                final int bottom = mThumbY + mThumbH;
-                final int left;
-                final int right;
-                switch (mList.getLayoutDirection()) {
-                    case View.LAYOUT_DIRECTION_RTL:
-                        left = 0;
-                        right = mThumbW;
-                        break;
-                    case View.LAYOUT_DIRECTION_LTR:
-                    default:
-                        left = viewWidth - mThumbW;
-                        right = viewWidth;
-                }
-                mList.invalidate(left, top, right, bottom);
-                break;
+    public void onSizeChanged(int w, int h, int oldw, int oldh) {
+        updateLayout();
+    }
+
+    public void onItemCountChanged(int oldTotalItemCount, int totalItemCount) {
+        final int visibleItemCount = mList.getChildCount();
+        final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
+        if (hasMoreItems && mState != STATE_DRAGGING) {
+            final int firstVisibleItem = mList.getFirstVisiblePosition();
+            setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
         }
-        mState = state;
-        refreshDrawableState();
     }
 
-    public int getState() {
-        return mState;
-    }
-
-    private void resetThumbPos() {
-        final int viewWidth = mList.getWidth();
-        // Bounds are always top right. Y coordinate get's translated during draw
-        switch (mPosition) {
-            case View.SCROLLBAR_POSITION_RIGHT:
-                mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
-                break;
-            case View.SCROLLBAR_POSITION_LEFT:
-                mThumbDrawable.setBounds(0, 0, mThumbW, mThumbH);
-                break;
-        }
-        mThumbDrawable.setAlpha(ScrollFade.ALPHA_MAX);
-    }
-
-    private void useThumbDrawable(Context context, Drawable drawable) {
-        mThumbDrawable = drawable;
-        if (drawable instanceof NinePatchDrawable) {
-            mThumbW = context.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.fastscroll_thumb_width);
-            mThumbH = context.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.fastscroll_thumb_height);
-        } else {
-            mThumbW = drawable.getIntrinsicWidth();
-            mThumbH = drawable.getIntrinsicHeight();
-        }
-        mChangedBounds = true;
-    }
-
-    private void init(Context context) {
-        // Get both the scrollbar states drawables
-        final TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS);
-        useThumbDrawable(context, ta.getDrawable(THUMB_DRAWABLE));
-        mTrackDrawable = ta.getDrawable(TRACK_DRAWABLE);
-
-        mOverlayDrawableLeft = ta.getDrawable(PREVIEW_BACKGROUND_LEFT);
-        mOverlayDrawableRight = ta.getDrawable(PREVIEW_BACKGROUND_RIGHT);
-        mOverlayPosition = ta.getInt(OVERLAY_POSITION, OVERLAY_FLOATING);
-
-        mScrollCompleted = true;
-
-        getSectionsFromIndexer();
-
+    /**
+     * Creates a view into which preview text can be placed.
+     */
+    private TextView createPreviewTextView(Context context, TypedArray ta) {
+        final LayoutParams params = new LayoutParams(
+                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
         final Resources res = context.getResources();
-        mOverlaySize = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.fastscroll_overlay_size);
-        mOverlayPadding = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.fastscroll_overlay_padding);
-        mOverlayPos = new RectF();
-        mScrollFade = new ScrollFade();
-        mPaint = new Paint();
-        mPaint.setAntiAlias(true);
-        mPaint.setTextAlign(Paint.Align.CENTER);
-        mPaint.setTextSize(mOverlaySize / 2);
+        final int minSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size);
+        final ColorStateList textColor = ta.getColorStateList(TEXT_COLOR);
+        final float textSize = res.getDimension(R.dimen.fastscroll_overlay_text_size);
+        final TextView textView = new TextView(context);
+        textView.setLayoutParams(params);
+        textView.setTextColor(textColor);
+        textView.setTextSize(textSize);
+        textView.setSingleLine(true);
+        textView.setEllipsize(TruncateAt.MIDDLE);
+        textView.setGravity(Gravity.CENTER);
+        textView.setAlpha(0f);
 
-        ColorStateList textColor = ta.getColorStateList(TEXT_COLOR);
-        int textColorNormal = textColor.getDefaultColor();
-        mPaint.setColor(textColorNormal);
-        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+        // Manually propagate inherited layout direction.
+        textView.setLayoutDirection(mList.getLayoutDirection());
 
-        // to show mOverlayDrawable properly
-        if (mList.getWidth() > 0 && mList.getHeight() > 0) {
-            onSizeChanged(mList.getWidth(), mList.getHeight(), 0, 0);
+        return textView;
+    }
+
+    /**
+     * Measures and layouts the scrollbar and decorations.
+     */
+    private void updateLayout() {
+        layoutThumb();
+        layoutTrack();
+
+        final Rect bounds = mTempBounds;
+        measurePreview(mPrimaryText, bounds);
+        applyLayout(mPrimaryText, bounds);
+        measurePreview(mSecondaryText, bounds);
+        applyLayout(mSecondaryText, bounds);
+
+        if (mPreviewImage != null) {
+            // Apply preview image padding.
+            bounds.left -= mPreviewImage.getPaddingLeft();
+            bounds.top -= mPreviewImage.getPaddingTop();
+            bounds.right += mPreviewImage.getPaddingRight();
+            bounds.bottom += mPreviewImage.getPaddingBottom();
+            applyLayout(mPreviewImage, bounds);
+        }
+    }
+
+    /**
+     * Layouts a view within the specified bounds and pins the pivot point to
+     * the appropriate edge.
+     *
+     * @param view The view to layout.
+     * @param bounds Bounds at which to layout the view.
+     */
+    private void applyLayout(View view, Rect bounds) {
+        view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
+        view.setPivotX(mLayoutFromRight ? bounds.right - bounds.left : 0);
+    }
+
+    /**
+     * Measures the preview text bounds, taking preview image padding into
+     * account. This method should only be called after {@link #layoutThumb()}
+     * and {@link #layoutTrack()} have both been called at least once.
+     *
+     * @param v The preview text view to measure.
+     * @param out Rectangle into which measured bounds are placed.
+     */
+    private void measurePreview(View v, Rect out) {
+        // Apply the preview image's padding as layout margins.
+        final Rect margins = mTempMargins;
+        margins.left = mPreviewImage.getPaddingLeft();
+        margins.top = mPreviewImage.getPaddingTop();
+        margins.right = mPreviewImage.getPaddingRight();
+        margins.bottom = mPreviewImage.getPaddingBottom();
+
+        if (mOverlayPosition == OVERLAY_AT_THUMB) {
+            measureViewToSide(v, mThumbImage, margins, out);
+        } else {
+            measureFloating(v, margins, out);
+        }
+    }
+
+    /**
+     * Measures the bounds for a view that should be laid out against the edge
+     * of an adjacent view. If no adjacent view is provided, lays out against
+     * the list edge.
+     *
+     * @param view The view to measure for layout.
+     * @param adjacent (Optional) The adjacent view, may be null to align to the
+     *            list edge.
+     * @param margins Layout margins to apply to the view.
+     * @param out Rectangle into which measured bounds are placed.
+     */
+    private void measureViewToSide(View view, View adjacent, Rect margins, Rect out) {
+        final int marginLeft;
+        final int marginTop;
+        final int marginRight;
+        if (margins == null) {
+            marginLeft = 0;
+            marginTop = 0;
+            marginRight = 0;
+        } else {
+            marginLeft = margins.left;
+            marginTop = margins.top;
+            marginRight = margins.right;
         }
 
-        mState = STATE_NONE;
-        refreshDrawableState();
+        final int listWidth = mList.getWidth();
+        final int maxWidth;
+        if (adjacent == null) {
+            maxWidth = listWidth;
+        } else if (mLayoutFromRight) {
+            maxWidth = adjacent.getLeft();
+        } else {
+            maxWidth = listWidth - adjacent.getRight();
+        }
 
-        ta.recycle();
+        final int adjMaxWidth = maxWidth - marginLeft - marginRight;
+        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
+        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        view.measure(widthMeasureSpec, heightMeasureSpec);
 
-        mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        // Align to the left or right.
+        final int width = view.getMeasuredWidth();
+        final int left;
+        final int right;
+        if (mLayoutFromRight) {
+            right = (adjacent == null ? listWidth : adjacent.getLeft()) - marginRight;
+            left = right - width;
+        } else {
+            left = (adjacent == null ? 0 : adjacent.getRight()) + marginLeft;
+            right = left + width;
+        }
 
-        mMatchDragPosition = context.getApplicationInfo().targetSdkVersion >=
-                android.os.Build.VERSION_CODES.HONEYCOMB;
-
-        setScrollbarPosition(mList.getVerticalScrollbarPosition());
+        // Don't adjust the vertical position.
+        final int top = marginTop;
+        final int bottom = top + view.getMeasuredHeight();
+        out.set(left, top, right, bottom);
     }
 
-    void stop() {
-        setState(STATE_NONE);
+    private void measureFloating(View preview, Rect margins, Rect out) {
+        final int marginLeft;
+        final int marginTop;
+        final int marginRight;
+        if (margins == null) {
+            marginLeft = 0;
+            marginTop = 0;
+            marginRight = 0;
+        } else {
+            marginLeft = margins.left;
+            marginTop = margins.top;
+            marginRight = margins.right;
+        }
+
+        final View list = mList;
+        final int listWidth = list.getWidth();
+        final int adjMaxWidth = listWidth - marginLeft - marginRight;
+        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST);
+        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        preview.measure(widthMeasureSpec, heightMeasureSpec);
+
+        // Align at the vertical center, 10% from the top.
+        final int width = preview.getMeasuredWidth();
+        final int top = list.getHeight() / 10 + marginTop;
+        final int bottom = top + preview.getMeasuredHeight();
+        final int left = (listWidth - width) / 2;
+        final int right = left + width;
+        out.set(left, top, right, bottom);
     }
 
-    boolean isVisible() {
-        return !(mState == STATE_NONE);
+    /**
+     * Lays out the thumb according to the current scrollbar position.
+     */
+    private void layoutThumb() {
+        final Rect bounds = mTempBounds;
+        measureViewToSide(mThumbImage, null, null, bounds);
+        applyLayout(mThumbImage, bounds);
     }
 
-    public void draw(Canvas canvas) {
+    /**
+     * Lays out the track centered on the thumb, if available, or against the
+     * edge if no thumb is available. Must be called after {@link #layoutThumb}.
+     */
+    private void layoutTrack() {
+        final View track = mTrackImage;
+        final View thumb = mThumbImage;
+        final View list = mList;
+        final int listWidth = list.getWidth();
+        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(listWidth, MeasureSpec.AT_MOST);
+        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        track.measure(widthMeasureSpec, heightMeasureSpec);
 
-        if (mState == STATE_NONE) {
-            // No need to draw anything
+        final int trackWidth = track.getMeasuredWidth();
+        final int thumbHalfHeight = thumb == null ? 0 : thumb.getHeight() / 2;
+        final int left = thumb == null ? listWidth - trackWidth :
+            thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2;
+        final int right = left + trackWidth;
+        final int top = thumbHalfHeight;
+        final int bottom = list.getHeight() - thumbHalfHeight;
+        track.layout(left, top, right, bottom);
+    }
+
+    private void setState(int state) {
+        mList.removeCallbacks(mDeferHide);
+
+        if (mAlwaysShow && state == STATE_NONE) {
+            state = STATE_VISIBLE;
+        }
+
+        if (state == mState) {
             return;
         }
 
-        final int y = mThumbY;
-        final int viewWidth = mList.getWidth();
-        final FastScroller.ScrollFade scrollFade = mScrollFade;
-
-        int alpha = -1;
-        if (mState == STATE_EXIT) {
-            alpha = scrollFade.getAlpha();
-            if (alpha < ScrollFade.ALPHA_MAX / 2) {
-                mThumbDrawable.setAlpha(alpha * 2);
-            }
-            int left = 0;
-            switch (mPosition) {
-                case View.SCROLLBAR_POSITION_RIGHT:
-                    left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
-                    break;
-                case View.SCROLLBAR_POSITION_LEFT:
-                    left = -mThumbW + (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
-                    break;
-            }
-            mThumbDrawable.setBounds(left, 0, left + mThumbW, mThumbH);
-            mChangedBounds = true;
+        switch (state) {
+            case STATE_NONE:
+                transitionToHidden();
+                break;
+            case STATE_VISIBLE:
+                transitionToVisible();
+                break;
+            case STATE_DRAGGING:
+                transitionToDragging();
+                break;
         }
 
-        if (mTrackDrawable != null) {
-            final Rect thumbBounds = mThumbDrawable.getBounds();
-            final int left = thumbBounds.left;
-            final int halfThumbHeight = (thumbBounds.bottom - thumbBounds.top) / 2;
-            final int trackWidth = mTrackDrawable.getIntrinsicWidth();
-            final int trackLeft = (left + mThumbW / 2) - trackWidth / 2;
-            mTrackDrawable.setBounds(trackLeft, halfThumbHeight,
-                    trackLeft + trackWidth, mList.getHeight() - halfThumbHeight);
-            mTrackDrawable.draw(canvas);
-        }
+        mState = state;
 
-        canvas.translate(0, y);
-        mThumbDrawable.draw(canvas);
-        canvas.translate(0, -y);
-
-        // If user is dragging the scroll bar, draw the alphabet overlay
-        if (mState == STATE_DRAGGING && mDrawOverlay) {
-            final Drawable overlay = mOverlayDrawable;
-            final Paint paint = mPaint;
-            final String sectionText = mSectionText;
-            final Rect tmpRect = mTmpRect;
-
-            // TODO: Use a text view in an overlay for transition animations and
-            // handling of text overflow.
-            paint.getTextBounds(sectionText, 0, sectionText.length(), tmpRect);
-            final int textWidth = tmpRect.width();
-            final int textHeight = tmpRect.height();
-
-            overlay.getPadding(tmpRect);
-            final int overlayWidth = Math.max(
-                    mOverlaySize, textWidth + tmpRect.left + tmpRect.right + mOverlayPadding * 2);
-            final int overlayHeight = Math.max(
-                    mOverlaySize, textHeight + tmpRect.top + tmpRect.bottom + mOverlayPadding * 2);
-            final RectF pos = mOverlayPos;
-
-            if (mOverlayPosition == OVERLAY_AT_THUMB) {
-                final Rect thumbBounds = mThumbDrawable.getBounds();
-
-                switch (mPosition) {
-                    case View.SCROLLBAR_POSITION_LEFT:
-                        pos.left = Math.min(
-                                thumbBounds.right + mThumbW, mList.getWidth() - overlayWidth);
-                        break;
-                    case View.SCROLLBAR_POSITION_RIGHT:
-                    default:
-                        pos.left = Math.max(0, thumbBounds.left - mThumbW - overlayWidth);
-                        break;
-                }
-
-                pos.top = Math.max(0, Math.min(
-                        y + (mThumbH - overlayHeight) / 2, mList.getHeight() - overlayHeight));
-            }
-
-            pos.right = pos.left + overlayWidth;
-            pos.bottom = pos.top + overlayHeight;
-
-            overlay.setBounds((int) pos.left, (int) pos.top, (int) pos.right, (int) pos.bottom);
-            overlay.draw(canvas);
-
-            final float hOff = (tmpRect.right - tmpRect.left) / 2.0f;
-            final float vOff = (tmpRect.bottom - tmpRect.top) / 2.0f;
-            final float cX = pos.centerX() - hOff;
-            final float cY = pos.centerY() + (overlayHeight / 4.0f) - paint.descent() - vOff;
-            canvas.drawText(mSectionText, cX, cY, paint);
-        } else if (mState == STATE_EXIT) {
-            if (alpha == 0) { // Done with exit
-                setState(STATE_NONE);
-            } else {
-                final int left, right, top, bottom;
-                if (mTrackDrawable != null) {
-                    top = 0;
-                    bottom = mList.getHeight();
-                } else {
-                    top = y;
-                    bottom = y + mThumbH;
-                }
-                switch (mList.getLayoutDirection()) {
-                    case View.LAYOUT_DIRECTION_RTL:
-                        left = 0;
-                        right = mThumbW;
-                        break;
-                    case View.LAYOUT_DIRECTION_LTR:
-                    default:
-                        left = viewWidth - mThumbW;
-                        right = viewWidth;
-                }
-                mList.invalidate(left, top, right, bottom);
-            }
-        }
+        refreshDrawablePressedState();
     }
 
-    void onSizeChanged(int w, int h, int oldw, int oldh) {
-        if (mThumbDrawable != null) {
-            switch (mPosition) {
-                default:
-                case View.SCROLLBAR_POSITION_RIGHT:
-                    mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH);
-                    break;
-                case View.SCROLLBAR_POSITION_LEFT:
-                    mThumbDrawable.setBounds(0, 0, mThumbW, mThumbH);
-                    break;
-            }
-        }
-        if (mOverlayPosition == OVERLAY_FLOATING) {
-            final RectF pos = mOverlayPos;
-            pos.left = (w - mOverlaySize) / 2;
-            pos.right = pos.left + mOverlaySize;
-            pos.top = h / 10; // 10% from top
-            pos.bottom = pos.top + mOverlaySize;
-            if (mOverlayDrawable != null) {
-                mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
-                        (int) pos.right, (int) pos.bottom);
-            }
-        }
+    private void refreshDrawablePressedState() {
+        final boolean isPressed = mState == STATE_DRAGGING;
+        mThumbImage.setPressed(isPressed);
+        mTrackImage.setPressed(isPressed);
     }
 
-    void onItemCountChanged(int oldCount, int newCount) {
-        if (mAlwaysShow) {
-            mLongList = true;
+    /**
+     * Shows nothing.
+     */
+    private void transitionToHidden() {
+        if (mDecorAnimation != null) {
+            mDecorAnimation.cancel();
         }
+
+        final Animator fadeOut = groupAnimatorOfFloat(View.ALPHA, 0f, mThumbImage, mTrackImage,
+                mPreviewImage, mPrimaryText, mSecondaryText).setDuration(DURATION_FADE_OUT);
+
+        // Push the thumb and track outside the list bounds.
+        final float offset = mLayoutFromRight ? mThumbImage.getWidth() : -mThumbImage.getWidth();
+        final Animator slideOut = groupAnimatorOfFloat(
+                View.TRANSLATION_X, offset, mThumbImage, mTrackImage)
+                .setDuration(DURATION_FADE_OUT);
+
+        mDecorAnimation = new AnimatorSet();
+        mDecorAnimation.playTogether(fadeOut, slideOut);
+        mDecorAnimation.start();
     }
 
-    void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
-            int totalItemCount) {
-        // Are there enough pages to require fast scroll? Recompute only if total count changes
+    /**
+     * Shows the thumb and track.
+     */
+    private void transitionToVisible() {
+        if (mDecorAnimation != null) {
+            mDecorAnimation.cancel();
+        }
+
+        final Animator fadeIn = groupAnimatorOfFloat(View.ALPHA, 1f, mThumbImage, mTrackImage)
+                .setDuration(DURATION_FADE_IN);
+        final Animator fadeOut = groupAnimatorOfFloat(
+                View.ALPHA, 0f, mPreviewImage, mPrimaryText, mSecondaryText)
+                .setDuration(DURATION_FADE_OUT);
+        final Animator slideIn = groupAnimatorOfFloat(
+                View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
+
+        mDecorAnimation = new AnimatorSet();
+        mDecorAnimation.playTogether(fadeIn, fadeOut, slideIn);
+        mDecorAnimation.start();
+    }
+
+    /**
+     * Shows the thumb, preview, and track.
+     */
+    private void transitionToDragging() {
+        if (mDecorAnimation != null) {
+            mDecorAnimation.cancel();
+        }
+
+        final Animator fadeIn = groupAnimatorOfFloat(
+                View.ALPHA, 1f, mThumbImage, mTrackImage, mPreviewImage)
+                .setDuration(DURATION_FADE_IN);
+        final Animator slideIn = groupAnimatorOfFloat(
+                View.TRANSLATION_X, 0f, mThumbImage, mTrackImage).setDuration(DURATION_FADE_IN);
+
+        mDecorAnimation = new AnimatorSet();
+        mDecorAnimation.playTogether(fadeIn, slideIn);
+        mDecorAnimation.start();
+
+        // Ensure the preview text is correct.
+        final String previewText = getPreviewText();
+        transitionPreviewLayout(previewText);
+    }
+
+    private boolean isLongList(int visibleItemCount, int totalItemCount) {
+        // Are there enough pages to require fast scroll? Recompute only if
+        // total count changes.
         if (mItemCount != totalItemCount && visibleItemCount > 0) {
             mItemCount = totalItemCount;
             mLongList = mItemCount / visibleItemCount >= MIN_PAGES;
         }
-        if (mAlwaysShow) {
-            mLongList = true;
-        }
-        if (!mLongList) {
-            if (mState != STATE_NONE) {
-                setState(STATE_NONE);
-            }
+
+        return mLongList;
+    }
+
+    public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+        if (!mAlwaysShow && !isLongList(visibleItemCount, totalItemCount)) {
+            setState(STATE_NONE);
             return;
         }
-        if (totalItemCount - visibleItemCount > 0 && mState != STATE_DRAGGING) {
-            mThumbY = getThumbPositionForListPosition(firstVisibleItem, visibleItemCount,
-                    totalItemCount);
-            if (mChangedBounds) {
-                resetThumbPos();
-                mChangedBounds = false;
-            }
+
+        final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
+        if (hasMoreItems && mState != STATE_DRAGGING) {
+            setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount));
         }
+
         mScrollCompleted = true;
-        if (firstVisibleItem == mVisibleItem) {
-            return;
-        }
-        mVisibleItem = firstVisibleItem;
-        if (mState != STATE_DRAGGING) {
-            setState(STATE_VISIBLE);
-            if (!mAlwaysShow) {
-                mHandler.postDelayed(mScrollFade, FADE_TIMEOUT);
+
+        if (mFirstVisibleItem != firstVisibleItem) {
+            mFirstVisibleItem = firstVisibleItem;
+
+            // Show the thumb, if necessary, and set up auto-fade.
+            if (mState != STATE_DRAGGING) {
+                setState(STATE_VISIBLE);
+                mList.postDelayed(mDeferHide, FADE_TIMEOUT);
             }
         }
     }
 
-    SectionIndexer getSectionIndexer() {
-        return mSectionIndexer;
-    }
-
-    Object[] getSections() {
-        if (mListAdapter == null && mList != null) {
-            getSectionsFromIndexer();
-        }
-        return mSections;
-    }
-
-    void getSectionsFromIndexer() {
-        Adapter adapter = mList.getAdapter();
+    private void getSectionsFromIndexer() {
         mSectionIndexer = null;
+
+        Adapter adapter = mList.getAdapter();
         if (adapter instanceof HeaderViewListAdapter) {
-            mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
-            adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
+            mHeaderCount = ((HeaderViewListAdapter) adapter).getHeadersCount();
+            adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter();
         }
+
         if (adapter instanceof ExpandableListConnector) {
-            ExpandableListAdapter expAdapter = ((ExpandableListConnector)adapter).getAdapter();
+            final ExpandableListAdapter expAdapter = ((ExpandableListConnector) adapter)
+                    .getAdapter();
             if (expAdapter instanceof SectionIndexer) {
                 mSectionIndexer = (SectionIndexer) expAdapter;
                 mListAdapter = (BaseAdapter) adapter;
                 mSections = mSectionIndexer.getSections();
             }
+        } else if (adapter instanceof SectionIndexer) {
+            mListAdapter = (BaseAdapter) adapter;
+            mSectionIndexer = (SectionIndexer) adapter;
+            mSections = mSectionIndexer.getSections();
         } else {
-            if (adapter instanceof SectionIndexer) {
-                mListAdapter = (BaseAdapter) adapter;
-                mSectionIndexer = (SectionIndexer) adapter;
-                mSections = mSectionIndexer.getSections();
-                if (mSections == null) {
-                    mSections = new String[] { " " };
-                }
-            } else {
-                mListAdapter = (BaseAdapter) adapter;
-                mSections = new String[] { " " };
-            }
+            mListAdapter = (BaseAdapter) adapter;
+            mSections = null;
         }
     }
 
@@ -564,21 +745,24 @@
         mListAdapter = null;
     }
 
-    void scrollTo(float position) {
-        int count = mList.getCount();
+    /**
+     * Scrolls to a specific position within the section
+     * @param position
+     */
+    private void scrollTo(float position) {
         mScrollCompleted = false;
-        float fThreshold = (1.0f / count) / 8;
+
+        final int count = mList.getCount();
         final Object[] sections = mSections;
+        final int sectionCount = sections == null ? 0 : sections.length;
         int sectionIndex;
-        if (sections != null && sections.length > 1) {
-            final int nSections = sections.length;
-            int section = (int) (position * nSections);
-            if (section >= nSections) {
-                section = nSections - 1;
-            }
-            int exactSection = section;
-            sectionIndex = section;
-            int index = mSectionIndexer.getPositionForSection(section);
+        if (sections != null && sectionCount > 1) {
+            final int exactSection = MathUtils.constrain(
+                    (int) (position * sectionCount), 0, sectionCount - 1);
+            int targetSection = exactSection;
+            int targetIndex = mSectionIndexer.getPositionForSection(targetSection);
+            sectionIndex = targetSection;
+
             // Given the expected section and index, the following code will
             // try to account for missing sections (no names starting with..)
             // It will compute the scroll space of surrounding empty sections
@@ -586,25 +770,26 @@
             // available space, so that there is always some list movement while
             // the user moves the thumb.
             int nextIndex = count;
-            int prevIndex = index;
-            int prevSection = section;
-            int nextSection = section + 1;
+            int prevIndex = targetIndex;
+            int prevSection = targetSection;
+            int nextSection = targetSection + 1;
+
             // Assume the next section is unique
-            if (section < nSections - 1) {
-                nextIndex = mSectionIndexer.getPositionForSection(section + 1);
+            if (targetSection < sectionCount - 1) {
+                nextIndex = mSectionIndexer.getPositionForSection(targetSection + 1);
             }
 
             // Find the previous index if we're slicing the previous section
-            if (nextIndex == index) {
+            if (nextIndex == targetIndex) {
                 // Non-existent letter
-                while (section > 0) {
-                    section--;
-                    prevIndex = mSectionIndexer.getPositionForSection(section);
-                    if (prevIndex != index) {
-                        prevSection = section;
-                        sectionIndex = section;
+                while (targetSection > 0) {
+                    targetSection--;
+                    prevIndex = mSectionIndexer.getPositionForSection(targetSection);
+                    if (prevIndex != targetIndex) {
+                        prevSection = targetSection;
+                        sectionIndex = targetSection;
                         break;
-                    } else if (section == 0) {
+                    } else if (targetSection == 0) {
                         // When section reaches 0 here, sectionIndex must follow it.
                         // Assuming mSectionIndexer.getPositionForSection(0) == 0.
                         sectionIndex = 0;
@@ -612,131 +797,281 @@
                     }
                 }
             }
+
             // Find the next index, in case the assumed next index is not
             // unique. For instance, if there is no P, then request for P's
             // position actually returns Q's. So we need to look ahead to make
             // sure that there is really a Q at Q's position. If not, move
             // further down...
             int nextNextSection = nextSection + 1;
-            while (nextNextSection < nSections &&
+            while (nextNextSection < sectionCount &&
                     mSectionIndexer.getPositionForSection(nextNextSection) == nextIndex) {
                 nextNextSection++;
                 nextSection++;
             }
+
             // Compute the beginning and ending scroll range percentage of the
-            // currently visible letter. This could be equal to or greater than
-            // (1 / nSections).
-            float fPrev = (float) prevSection / nSections;
-            float fNext = (float) nextSection / nSections;
-            if (prevSection == exactSection && position - fPrev < fThreshold) {
-                index = prevIndex;
+            // currently visible section. This could be equal to or greater than
+            // (1 / nSections). If the target position is near the previous
+            // position, snap to the previous position.
+            final float prevPosition = (float) prevSection / sectionCount;
+            final float nextPosition = (float) nextSection / sectionCount;
+            final float snapThreshold = (count == 0) ? Float.MAX_VALUE : .125f / count;
+            if (prevSection == exactSection && position - prevPosition < snapThreshold) {
+                targetIndex = prevIndex;
             } else {
-                index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev)
-                    / (fNext - fPrev));
+                targetIndex = prevIndex + (int) ((nextIndex - prevIndex) * (position - prevPosition)
+                    / (nextPosition - prevPosition));
             }
-            // Don't overflow
-            if (index > count - 1) index = count - 1;
+
+            // Clamp to valid positions.
+            targetIndex = MathUtils.constrain(targetIndex, 0, count - 1);
 
             if (mList instanceof ExpandableListView) {
-                ExpandableListView expList = (ExpandableListView) mList;
+                final ExpandableListView expList = (ExpandableListView) mList;
                 expList.setSelectionFromTop(expList.getFlatListPosition(
-                        ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0);
+                        ExpandableListView.getPackedPositionForGroup(targetIndex + mHeaderCount)),
+                        0);
             } else if (mList instanceof ListView) {
-                ((ListView)mList).setSelectionFromTop(index + mListOffset, 0);
+                ((ListView) mList).setSelectionFromTop(targetIndex + mHeaderCount, 0);
             } else {
-                mList.setSelection(index + mListOffset);
+                mList.setSelection(targetIndex + mHeaderCount);
             }
         } else {
-            int index = (int) (position * count);
-            // Don't overflow
-            if (index > count - 1) index = count - 1;
+            final int index = MathUtils.constrain((int) (position * count), 0, count - 1);
 
             if (mList instanceof ExpandableListView) {
                 ExpandableListView expList = (ExpandableListView) mList;
                 expList.setSelectionFromTop(expList.getFlatListPosition(
-                        ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0);
+                        ExpandableListView.getPackedPositionForGroup(index + mHeaderCount)), 0);
             } else if (mList instanceof ListView) {
-                ((ListView)mList).setSelectionFromTop(index + mListOffset, 0);
+                ((ListView)mList).setSelectionFromTop(index + mHeaderCount, 0);
             } else {
-                mList.setSelection(index + mListOffset);
+                mList.setSelection(index + mHeaderCount);
             }
+
             sectionIndex = -1;
         }
 
-        if (sectionIndex >= 0) {
-            String text = mSectionText = sections[sectionIndex].toString();
-            mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
-                    sectionIndex < sections.length;
+        if (sectionIndex >= 0 && sectionIndex < sections.length) {
+            // If we moved sections, display section.
+            if (mCurrentSection != sectionIndex) {
+                mCurrentSection = sectionIndex;
+                final String section = sections[sectionIndex].toString();
+                transitionToDragging();
+                transitionPreviewLayout(section);
+            }
         } else {
-            mDrawOverlay = false;
+            // No current section, transition out of preview.
+            transitionPreviewLayout(null);
+            transitionToVisible();
         }
     }
 
-    private int getThumbPositionForListPosition(int firstVisibleItem, int visibleItemCount,
-            int totalItemCount) {
+    private String getPreviewText() {
+        final Object[] sections = mSections;
+        if (sections == null) {
+            return null;
+        }
+
+        final int sectionIndex = mCurrentSection;
+        if (sectionIndex < 0 || sectionIndex >= sections.length) {
+            return null;
+        }
+
+        return sections[sectionIndex].toString();
+    }
+
+    /**
+     * Transitions the preview text to a new value. Handles animation,
+     * measurement, and layout.
+     *
+     * @param text The preview text to transition to.
+     */
+    private void transitionPreviewLayout(CharSequence text) {
+        final Rect bounds = mTempBounds;
+        final ImageView preview = mPreviewImage;
+        final TextView showing;
+        final TextView target;
+        if (mShowingPrimary) {
+            showing = mPrimaryText;
+            target = mSecondaryText;
+        } else {
+            showing = mSecondaryText;
+            target = mPrimaryText;
+        }
+
+        // Set and layout target immediately.
+        target.setText(text);
+        measurePreview(target, bounds);
+        applyLayout(target, bounds);
+
+        if (mPreviewAnimation != null) {
+            mPreviewAnimation.cancel();
+        }
+
+        // Cross-fade preview text.
+        final Animator showTarget = animateAlpha(target, 1f).setDuration(DURATION_CROSS_FADE);
+        final Animator hideShowing = animateAlpha(showing, 0f).setDuration(DURATION_CROSS_FADE);
+        hideShowing.addListener(mSwitchPrimaryListener);
+
+        // Apply preview image padding and animate bounds, if necessary.
+        bounds.left -= mPreviewImage.getPaddingLeft();
+        bounds.top -= mPreviewImage.getPaddingTop();
+        bounds.right += mPreviewImage.getPaddingRight();
+        bounds.bottom += mPreviewImage.getPaddingBottom();
+        final Animator resizePreview = animateBounds(preview, bounds);
+        resizePreview.setDuration(DURATION_RESIZE);
+
+        mPreviewAnimation = new AnimatorSet();
+        final AnimatorSet.Builder builder = mPreviewAnimation.play(hideShowing).with(showTarget);
+        builder.with(resizePreview);
+
+        // The current preview size is unaffected by hidden or showing. It's
+        // used to set starting scales for things that need to be scaled down.
+        final int previewWidth = preview.getWidth() - preview.getPaddingLeft()
+                - preview.getPaddingRight();
+
+        // If target is too large, shrink it immediately to fit and expand to
+        // target size. Otherwise, start at target size.
+        final int targetWidth = target.getWidth();
+        if (targetWidth > previewWidth) {
+            target.setScaleX((float) previewWidth / targetWidth);
+            final Animator scaleAnim = animateScaleX(target, 1f).setDuration(DURATION_RESIZE);
+            builder.with(scaleAnim);
+        } else {
+            target.setScaleX(1f);
+        }
+
+        // If showing is larger than target, shrink to target size.
+        final int showingWidth = showing.getWidth();
+        if (showingWidth > targetWidth) {
+            final float scale = (float) targetWidth / showingWidth;
+            final Animator scaleAnim = animateScaleX(showing, scale).setDuration(DURATION_RESIZE);
+            builder.with(scaleAnim);
+        }
+
+        mPreviewAnimation.start();
+    }
+
+    /**
+     * Positions the thumb and preview widgets.
+     *
+     * @param position The position, between 0 and 1, along the track at which
+     *            to place the thumb.
+     */
+    private void setThumbPos(float position) {
+        final int top = 0;
+        final int bottom = mList.getHeight();
+
+        final float thumbHalfHeight = mThumbImage.getHeight() / 2f;
+        final float min = top + thumbHalfHeight;
+        final float max = bottom - thumbHalfHeight;
+        final float offset = min;
+        final float range = max - min;
+        final float thumbMiddle = position * range + offset;
+        mThumbImage.setTranslationY(thumbMiddle - thumbHalfHeight);
+
+        // Center the preview on the thumb, constrained to the list bounds.
+        final float previewHalfHeight = mPreviewImage.getHeight() / 2f;
+        final float minP = top + previewHalfHeight;
+        final float maxP = bottom - previewHalfHeight;
+        final float previewMiddle = MathUtils.constrain(thumbMiddle, minP, maxP);
+        final float previewTop = previewMiddle - previewHalfHeight;
+
+        mPreviewImage.setTranslationY(previewTop);
+        mPrimaryText.setTranslationY(previewTop);
+        mSecondaryText.setTranslationY(previewTop);
+    }
+
+    private float getPosFromMotionEvent(float y) {
+        final int top = 0;
+        final int bottom = mList.getHeight();
+
+        final float thumbHalfHeight = mThumbImage.getHeight() / 2f;
+        final float min = top + thumbHalfHeight;
+        final float max = bottom - thumbHalfHeight;
+        final float offset = min;
+        final float range = max - min;
+
+        // If the list is the same height as the thumbnail or shorter,
+        // effectively disable scrolling.
+        if (range <= 0) {
+            return 0f;
+        }
+
+        return MathUtils.constrain((y - offset) / range, 0f, 1f);
+    }
+
+    private float getPosFromItemCount(
+            int firstVisibleItem, int visibleItemCount, int totalItemCount) {
         if (mSectionIndexer == null || mListAdapter == null) {
             getSectionsFromIndexer();
         }
-        if (mSectionIndexer == null || !mMatchDragPosition) {
-            return ((mList.getHeight() - mThumbH) * firstVisibleItem)
-                    / (totalItemCount - visibleItemCount);
+
+        final boolean hasSections = mSectionIndexer != null && mSections != null
+                && mSections.length > 0;
+        if (!hasSections || !mMatchDragPosition) {
+            return firstVisibleItem / (totalItemCount - visibleItemCount);
         }
 
-        firstVisibleItem -= mListOffset;
+        firstVisibleItem -= mHeaderCount;
         if (firstVisibleItem < 0) {
             return 0;
         }
-        totalItemCount -= mListOffset;
 
-        final int trackHeight = mList.getHeight() - mThumbH;
+        totalItemCount -= mHeaderCount;
 
         final int section = mSectionIndexer.getSectionForPosition(firstVisibleItem);
         final int sectionPos = mSectionIndexer.getPositionForSection(section);
         final int nextSectionPos = mSectionIndexer.getPositionForSection(section + 1);
         final int sectionCount = mSections.length;
-        final int positionsInSection = nextSectionPos - sectionPos;
+        final int positionsInSection = Math.max(1, nextSectionPos - sectionPos);
 
         final View child = mList.getChildAt(0);
         final float incrementalPos = child == null ? 0 : firstVisibleItem +
-                (float) (mList.getPaddingTop() - child.getTop()) / child.getHeight();
+                (float) (mList.getPaddingTop() - child.getTop()) / Math.max(1, child.getHeight());
         final float posWithinSection = (incrementalPos - sectionPos) / positionsInSection;
-        int result = (int) ((section + posWithinSection) / sectionCount * trackHeight);
-
-        // Fake out the scrollbar for the last item. Since the section indexer won't
-        // ever actually move the list in this end space, make scrolling across the last item
-        // account for whatever space is remaining.
-        if (firstVisibleItem > 0 && firstVisibleItem + visibleItemCount == totalItemCount) {
-            final View lastChild = mList.getChildAt(visibleItemCount - 1);
-            final float lastItemVisible = (float) (mList.getHeight() - mList.getPaddingBottom()
-                    - lastChild.getTop()) / lastChild.getHeight();
-            result += (trackHeight - result) * lastItemVisible;
-        }
-
-        return result;
+        return (section + posWithinSection) / sectionCount;
     }
 
+    /**
+     * Cancels an ongoing fling event by injecting a
+     * {@link MotionEvent#ACTION_CANCEL} into the host view.
+     */
     private void cancelFling() {
-        // Cancel the list fling
-        MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+        final MotionEvent cancelFling = MotionEvent.obtain(
+                0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
         mList.onTouchEvent(cancelFling);
         cancelFling.recycle();
     }
 
-    void cancelPendingDrag() {
+    /**
+     * Cancels a pending drag.
+     *
+     * @see #startPendingDrag()
+     */
+    private void cancelPendingDrag() {
         mList.removeCallbacks(mDeferStartDrag);
-        mPendingDrag = false;
+        mHasPendingDrag = false;
     }
 
-    void startPendingDrag() {
-        mPendingDrag = true;
-        mList.postDelayed(mDeferStartDrag, PENDING_DRAG_DELAY);
+    /**
+     * Delays dragging until after the framework has determined that the user is
+     * scrolling, rather than tapping.
+     */
+    private void startPendingDrag() {
+        mHasPendingDrag = true;
+        mList.postDelayed(mDeferStartDrag, TAP_TIMEOUT);
     }
 
-    void beginDrag() {
+    private void beginDrag() {
         setState(STATE_DRAGGING);
+
         if (mListAdapter == null && mList != null) {
             getSectionsFromIndexer();
         }
+
         if (mList != null) {
             mList.requestDisallowInterceptTouchEvent(true);
             mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
@@ -745,16 +1080,23 @@
         cancelFling();
     }
 
-    boolean onInterceptTouchEvent(MotionEvent ev) {
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
-                if (mState > STATE_NONE && isPointInside(ev.getX(), ev.getY())) {
-                    if (!mList.isInScrollingContainer()) {
-                        beginDrag();
-                        return true;
+                if (isPointInside(ev.getX(), ev.getY())) {
+                    // If the parent has requested that its children delay
+                    // pressed state (e.g. is a scrolling container) then we
+                    // need to allow the parent time to decide whether it wants
+                    // to intercept events. If it does, we will receive a CANCEL
+                    // event.
+                    if (mList.isInScrollingContainer()) {
+                        mInitialTouchY = ev.getY();
+                        startPendingDrag();
+                        return false;
                     }
-                    mInitialTouchY = ev.getY();
-                    startPendingDrag();
+
+                    beginDrag();
+                    return true;
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -762,70 +1104,56 @@
                 cancelPendingDrag();
                 break;
         }
+
         return false;
     }
 
-    boolean onTouchEvent(MotionEvent me) {
-        if (mState == STATE_NONE) {
-            return false;
-        }
-
-        final int action = me.getAction();
-
-        if (action == MotionEvent.ACTION_DOWN) {
-            if (isPointInside(me.getX(), me.getY())) {
-                if (!mList.isInScrollingContainer()) {
+    public boolean onTouchEvent(MotionEvent me) {
+        switch (me.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                if (isPointInside(me.getX(), me.getY())) {
                     beginDrag();
                     return true;
                 }
-                mInitialTouchY = me.getY();
-                startPendingDrag();
-            }
-        } else if (action == MotionEvent.ACTION_UP) { // don't add ACTION_CANCEL here
-            if (mPendingDrag) {
-                // Allow a tap to scroll.
-                beginDrag();
+            } break;
 
-                final int viewHeight = mList.getHeight();
-                // Jitter
-                int newThumbY = (int) me.getY() - mThumbH + 10;
-                if (newThumbY < 0) {
-                    newThumbY = 0;
-                } else if (newThumbY + mThumbH > viewHeight) {
-                    newThumbY = viewHeight - mThumbH;
-                }
-                mThumbY = newThumbY;
-                scrollTo((float) mThumbY / (viewHeight - mThumbH));
+            case MotionEvent.ACTION_UP: {
+                if (mHasPendingDrag) {
+                    // Allow a tap to scroll.
+                    beginDrag();
 
-                cancelPendingDrag();
-                // Will hit the STATE_DRAGGING check below
-            }
-            if (mState == STATE_DRAGGING) {
-                if (mList != null) {
-                    // ViewGroup does the right thing already, but there might
-                    // be other classes that don't properly reset on touch-up,
-                    // so do this explicitly just in case.
-                    mList.requestDisallowInterceptTouchEvent(false);
-                    mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
-                }
-                setState(STATE_VISIBLE);
-                final Handler handler = mHandler;
-                handler.removeCallbacks(mScrollFade);
-                if (!mAlwaysShow) {
-                    handler.postDelayed(mScrollFade, 1000);
+                    final float pos = getPosFromMotionEvent(me.getY());
+                    setThumbPos(pos);
+                    scrollTo(pos);
+
+                    cancelPendingDrag();
+                    // Will hit the STATE_DRAGGING check below
                 }
 
-                mList.invalidate();
-                return true;
-            }
-        } else if (action == MotionEvent.ACTION_MOVE) {
-            if (mPendingDrag) {
-                final float y = me.getY();
-                if (Math.abs(y - mInitialTouchY) > mScaledTouchSlop) {
+                if (mState == STATE_DRAGGING) {
+                    if (mList != null) {
+                        // ViewGroup does the right thing already, but there might
+                        // be other classes that don't properly reset on touch-up,
+                        // so do this explicitly just in case.
+                        mList.requestDisallowInterceptTouchEvent(false);
+                        mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+                    }
+
+                    setState(STATE_VISIBLE);
+                    mList.postDelayed(mDeferHide, FADE_TIMEOUT);
+
+                    return true;
+                }
+            } break;
+
+            case MotionEvent.ACTION_MOVE: {
+                if (mHasPendingDrag && Math.abs(me.getY() - mInitialTouchY) > mScaledTouchSlop) {
                     setState(STATE_DRAGGING);
+
                     if (mListAdapter == null && mList != null) {
                         getSectionsFromIndexer();
                     }
+
                     if (mList != null) {
                         mList.requestDisallowInterceptTouchEvent(true);
                         mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
@@ -835,87 +1163,168 @@
                     cancelPendingDrag();
                     // Will hit the STATE_DRAGGING check below
                 }
-            }
-            if (mState == STATE_DRAGGING) {
-                final int viewHeight = mList.getHeight();
-                // Jitter
-                int newThumbY = (int) me.getY() - mThumbH + 10;
-                if (newThumbY < 0) {
-                    newThumbY = 0;
-                } else if (newThumbY + mThumbH > viewHeight) {
-                    newThumbY = viewHeight - mThumbH;
-                }
-                if (Math.abs(mThumbY - newThumbY) < 2) {
+
+                if (mState == STATE_DRAGGING) {
+                    // TODO: Ignore jitter.
+                    final float pos = getPosFromMotionEvent(me.getY());
+                    setThumbPos(pos);
+
+                    // If the previous scrollTo is still pending
+                    if (mScrollCompleted) {
+                        scrollTo(pos);
+                    }
+
                     return true;
                 }
-                mThumbY = newThumbY;
-                // If the previous scrollTo is still pending
-                if (mScrollCompleted) {
-                    scrollTo((float) mThumbY / (viewHeight - mThumbH));
-                }
-                return true;
-            }
-        } else if (action == MotionEvent.ACTION_CANCEL) {
-            cancelPendingDrag();
+            } break;
+
+            case MotionEvent.ACTION_CANCEL: {
+                cancelPendingDrag();
+            } break;
         }
+
         return false;
     }
 
-    boolean isPointInside(float x, float y) {
-        boolean inTrack = false;
-        switch (mPosition) {
-            default:
-            case View.SCROLLBAR_POSITION_RIGHT:
-                inTrack = x > mList.getWidth() - mThumbW;
-                break;
-            case View.SCROLLBAR_POSITION_LEFT:
-                inTrack = x < mThumbW;
-                break;
-        }
-
-        // Allow taps in the track to start moving.
-        return inTrack && (mTrackDrawable != null || y >= mThumbY && y <= mThumbY + mThumbH);
+    /**
+     * Returns whether a coordinate is inside the scroller's activation area. If
+     * there is a track image, touching anywhere within the thumb-width of the
+     * track activates scrolling. Otherwise, the user has to touch inside thumb
+     * itself.
+     *
+     * @param x The x-coordinate.
+     * @param y The y-coordinate.
+     * @return Whether the coordinate is inside the scroller's activation area.
+     */
+    private boolean isPointInside(float x, float y) {
+        return isPointInsideX(x) && (mHasTrackImage || isPointInsideY(y));
     }
 
-    public class ScrollFade implements Runnable {
+    private boolean isPointInsideX(float x) {
+        if (mLayoutFromRight) {
+            return x >= mThumbImage.getLeft();
+        } else {
+            return x <= mThumbImage.getRight();
+        }
+    }
 
-        long mStartTime;
-        long mFadeDuration;
-        static final int ALPHA_MAX = 208;
-        static final long FADE_DURATION = 200;
+    private boolean isPointInsideY(float y) {
+        return y >= mThumbImage.getTop() && y <= mThumbImage.getBottom();
+    }
 
-        void startFade() {
-            mFadeDuration = FADE_DURATION;
-            mStartTime = SystemClock.uptimeMillis();
-            setState(STATE_EXIT);
+    /**
+     * Constructs an animator for the specified property on a group of views.
+     * See {@link ObjectAnimator#ofFloat(Object, String, float...)} for
+     * implementation details.
+     *
+     * @param property The property being animated.
+     * @param value The value to which that property should animate.
+     * @param views The target views to animate.
+     * @return An animator for all the specified views.
+     */
+    private static Animator groupAnimatorOfFloat(
+            Property<View, Float> property, float value, View... views) {
+        AnimatorSet animSet = new AnimatorSet();
+        AnimatorSet.Builder builder = null;
+
+        for (int i = views.length - 1; i >= 0; i--) {
+            final Animator anim = ObjectAnimator.ofFloat(views[i], property, value);
+            if (builder == null) {
+                builder = animSet.play(anim);
+            } else {
+                builder.with(anim);
+            }
         }
 
-        int getAlpha() {
-            if (getState() != STATE_EXIT) {
-                return ALPHA_MAX;
-            }
-            int alpha;
-            long now = SystemClock.uptimeMillis();
-            if (now > mStartTime + mFadeDuration) {
-                alpha = 0;
-            } else {
-                alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration);
-            }
-            return alpha;
+        return animSet;
+    }
+
+    /**
+     * Returns an animator for the view's scaleX value.
+     */
+    private static Animator animateScaleX(View v, float target) {
+        return ObjectAnimator.ofFloat(v, View.SCALE_X, target);
+    }
+
+    /**
+     * Returns an animator for the view's alpha value.
+     */
+    private static Animator animateAlpha(View v, float alpha) {
+        return ObjectAnimator.ofFloat(v, View.ALPHA, alpha);
+    }
+
+    /**
+     * A Property wrapper around the <code>left</code> functionality handled by the
+     * {@link View#setLeft(int)} and {@link View#getLeft()} methods.
+     */
+    private static Property<View, Integer> LEFT = new IntProperty<View>("left") {
+        @Override
+        public void setValue(View object, int value) {
+            object.setLeft(value);
         }
 
         @Override
-        public void run() {
-            if (getState() != STATE_EXIT) {
-                startFade();
-                return;
-            }
-
-            if (getAlpha() > 0) {
-                mList.invalidate();
-            } else {
-                setState(STATE_NONE);
-            }
+        public Integer get(View object) {
+            return object.getLeft();
         }
+    };
+
+    /**
+     * A Property wrapper around the <code>top</code> functionality handled by the
+     * {@link View#setTop(int)} and {@link View#getTop()} methods.
+     */
+    private static Property<View, Integer> TOP = new IntProperty<View>("top") {
+        @Override
+        public void setValue(View object, int value) {
+            object.setTop(value);
+        }
+
+        @Override
+        public Integer get(View object) {
+            return object.getTop();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>right</code> functionality handled by the
+     * {@link View#setRight(int)} and {@link View#getRight()} methods.
+     */
+    private static Property<View, Integer> RIGHT = new IntProperty<View>("right") {
+        @Override
+        public void setValue(View object, int value) {
+            object.setRight(value);
+        }
+
+        @Override
+        public Integer get(View object) {
+            return object.getRight();
+        }
+    };
+
+    /**
+     * A Property wrapper around the <code>bottom</code> functionality handled by the
+     * {@link View#setBottom(int)} and {@link View#getBottom()} methods.
+     */
+    private static Property<View, Integer> BOTTOM = new IntProperty<View>("bottom") {
+        @Override
+        public void setValue(View object, int value) {
+            object.setBottom(value);
+        }
+
+        @Override
+        public Integer get(View object) {
+            return object.getBottom();
+        }
+    };
+
+    /**
+     * Returns an animator for the view's bounds.
+     */
+    private static Animator animateBounds(View v, Rect bounds) {
+        final PropertyValuesHolder left = PropertyValuesHolder.ofInt(LEFT, bounds.left);
+        final PropertyValuesHolder top = PropertyValuesHolder.ofInt(TOP, bounds.top);
+        final PropertyValuesHolder right = PropertyValuesHolder.ofInt(RIGHT, bounds.right);
+        final PropertyValuesHolder bottom = PropertyValuesHolder.ofInt(BOTTOM, bounds.bottom);
+        return ObjectAnimator.ofPropertyValuesHolder(v, left, top, right, bottom);
     }
 }
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index a7a9266..016c11e 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -296,23 +296,11 @@
     SkAutoTDelete<SkBitmap> adb(outputBitmap == NULL ? new SkBitmap : NULL);
     if (outputBitmap == NULL) outputBitmap = adb.get();
 
-    SkAutoTDelete<SkImageDecoder> add(decoder);
-
     NinePatchPeeker peeker(decoder);
     decoder->setPeeker(&peeker);
 
-    AutoDecoderCancel adc(options, decoder);
-
-    // To fix the race condition in case "requestCancelDecode"
-    // happens earlier than AutoDecoderCancel object is added
-    // to the gAutoDecoderCancelMutex linked list.
-    if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) {
-        return nullObjectReturn("gOptions_mCancelID");
-    }
-
     SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode;
 
-
     JavaPixelAllocator javaAllocator(env);
     RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize);
     ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
@@ -328,6 +316,20 @@
         }
     }
 
+    // Only setup the decoder to be deleted after its stack-based, refcounted
+    // components (allocators, peekers, etc) are declared. This prevents RefCnt
+    // asserts from firing due to the order objects are deleted from the stack.
+    SkAutoTDelete<SkImageDecoder> add(decoder);
+
+    AutoDecoderCancel adc(options, decoder);
+
+    // To fix the race condition in case "requestCancelDecode"
+    // happens earlier than AutoDecoderCancel object is added
+    // to the gAutoDecoderCancelMutex linked list.
+    if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) {
+        return nullObjectReturn("gOptions_mCancelID");
+    }
+
     SkBitmap decodingBitmap;
     if (!decoder->decode(stream, &decodingBitmap, prefConfig, decodeMode)) {
         return nullObjectReturn("decoder->decode returned false");
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ccca2d8..00caac9 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -52,6 +52,8 @@
 
     <!-- Minimum size of the fastscroll overlay -->
     <dimen name="fastscroll_overlay_size">104dp</dimen>
+    <!-- Text size of the fastscroll overlay -->
+    <dimen name="fastscroll_overlay_text_size">24sp</dimen>
     <!-- Padding of the fastscroll overlay -->
     <dimen name="fastscroll_overlay_padding">16dp</dimen>
     <!-- Width of the fastscroll thumb -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cb8d144..7f39364 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -309,6 +309,7 @@
   <java-symbol type="dimen" name="dropdownitem_icon_width" />
   <java-symbol type="dimen" name="dropdownitem_text_padding_left" />
   <java-symbol type="dimen" name="fastscroll_overlay_size" />
+  <java-symbol type="dimen" name="fastscroll_overlay_text_size" />
   <java-symbol type="dimen" name="fastscroll_overlay_padding" />
   <java-symbol type="dimen" name="fastscroll_thumb_height" />
   <java-symbol type="dimen" name="fastscroll_thumb_width" />
diff --git a/opengl/java/android/opengl/EGL14.java b/opengl/java/android/opengl/EGL14.java
index cd53c17..b93557d 100644
--- a/opengl/java/android/opengl/EGL14.java
+++ b/opengl/java/android/opengl/EGL14.java
@@ -1,5 +1,4 @@
 /*
-**
 ** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/opengl/java/android/opengl/EGLConfig.java b/opengl/java/android/opengl/EGLConfig.java
index d457c9f..a7a6bbb 100644
--- a/opengl/java/android/opengl/EGLConfig.java
+++ b/opengl/java/android/opengl/EGLConfig.java
@@ -29,7 +29,7 @@
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
+        if (!(o instanceof EGLConfig)) return false;
 
         EGLConfig that = (EGLConfig) o;
         return getHandle() == that.getHandle();
diff --git a/opengl/java/android/opengl/EGLContext.java b/opengl/java/android/opengl/EGLContext.java
index 41b8ef1..c93bd6e 100644
--- a/opengl/java/android/opengl/EGLContext.java
+++ b/opengl/java/android/opengl/EGLContext.java
@@ -29,7 +29,7 @@
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
+        if (!(o instanceof EGLContext)) return false;
 
         EGLContext that = (EGLContext) o;
         return getHandle() == that.getHandle();
diff --git a/opengl/java/android/opengl/EGLDisplay.java b/opengl/java/android/opengl/EGLDisplay.java
index 17d1a64..5b8043a 100644
--- a/opengl/java/android/opengl/EGLDisplay.java
+++ b/opengl/java/android/opengl/EGLDisplay.java
@@ -29,7 +29,7 @@
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
+        if (!(o instanceof EGLDisplay)) return false;
 
         EGLDisplay that = (EGLDisplay) o;
         return getHandle() == that.getHandle();
diff --git a/opengl/java/android/opengl/EGLExt.java b/opengl/java/android/opengl/EGLExt.java
index 2e0363d1..b74b5fb 100644
--- a/opengl/java/android/opengl/EGLExt.java
+++ b/opengl/java/android/opengl/EGLExt.java
@@ -1,5 +1,4 @@
 /*
-**
 ** Copyright 2013, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/opengl/java/android/opengl/EGLSurface.java b/opengl/java/android/opengl/EGLSurface.java
index 65bec4f..c379dc9 100644
--- a/opengl/java/android/opengl/EGLSurface.java
+++ b/opengl/java/android/opengl/EGLSurface.java
@@ -29,7 +29,7 @@
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
+        if (!(o instanceof EGLSurface)) return false;
 
         EGLSurface that = (EGLSurface) o;
         return getHandle() == that.getHandle();
diff --git a/opengl/java/android/opengl/Matrix.java b/opengl/java/android/opengl/Matrix.java
index 72128ac..ce3f57e 100644
--- a/opengl/java/android/opengl/Matrix.java
+++ b/opengl/java/android/opengl/Matrix.java
@@ -19,24 +19,21 @@
 /**
  * Matrix math utilities. These methods operate on OpenGL ES format
  * matrices and vectors stored in float arrays.
- *
+ * <p>
  * Matrices are 4 x 4 column-vector matrices stored in column-major
  * order:
  * <pre>
  *  m[offset +  0] m[offset +  4] m[offset +  8] m[offset + 12]
  *  m[offset +  1] m[offset +  5] m[offset +  9] m[offset + 13]
  *  m[offset +  2] m[offset +  6] m[offset + 10] m[offset + 14]
- *  m[offset +  3] m[offset +  7] m[offset + 11] m[offset + 15]
- * </pre>
+ *  m[offset +  3] m[offset +  7] m[offset + 11] m[offset + 15]</pre>
  *
- * Vectors are 4 row x 1 column column-vectors stored in order:
+ * Vectors are 4 x 1 column vectors stored in order:
  * <pre>
  * v[offset + 0]
  * v[offset + 1]
  * v[offset + 2]
- * v[offset + 3]
- * </pre>
- *
+ * v[offset + 3]</pre>
  */
 public class Matrix {
 
@@ -44,12 +41,18 @@
     private final static float[] sTemp = new float[32];
 
     /**
-     * Multiply two 4x4 matrices together and store the result in a third 4x4
+     * @deprecated All methods are static, do not instantiate this class.
+     */
+    @Deprecated
+    public Matrix() {}
+
+    /**
+     * Multiplies two 4x4 matrices together and stores the result in a third 4x4
      * matrix. In matrix notation: result = lhs x rhs. Due to the way
      * matrix multiplication works, the result matrix will have the same
      * effect as first multiplying by the rhs matrix, then multiplying by
      * the lhs matrix. This is the opposite of what you might expect.
-     *
+     * <p>
      * The same float array may be passed for result, lhs, and/or rhs. However,
      * the result element values are undefined if the result elements overlap
      * either the lhs or rhs elements.
@@ -70,9 +73,9 @@
             float[] lhs, int lhsOffset, float[] rhs, int rhsOffset);
 
     /**
-     * Multiply a 4 element vector by a 4x4 matrix and store the result in a 4
-     * element column vector. In matrix notation: result = lhs x rhs
-     *
+     * Multiplies a 4 element vector by a 4x4 matrix and stores the result in a
+     * 4-element column vector. In matrix notation: result = lhs x rhs
+     * <p>
      * The same float array may be passed for resultVec, lhsMat, and/or rhsVec.
      * However, the resultVec element values are undefined if the resultVec
      * elements overlap either the lhsMat or rhsVec elements.
@@ -97,12 +100,14 @@
 
     /**
      * Transposes a 4 x 4 matrix.
+     * <p>
+     * mTrans and m must not overlap.
      *
-     * @param mTrans the array that holds the output inverted matrix
-     * @param mTransOffset an offset into mInv where the inverted matrix is
+     * @param mTrans the array that holds the output transposed matrix
+     * @param mTransOffset an offset into mTrans where the transposed matrix is
      *        stored.
      * @param m the input array
-     * @param mOffset an offset into m where the matrix is stored.
+     * @param mOffset an offset into m where the input matrix is stored.
      */
     public static void transposeM(float[] mTrans, int mTransOffset, float[] m,
             int mOffset) {
@@ -117,12 +122,14 @@
 
     /**
      * Inverts a 4 x 4 matrix.
+     * <p>
+     * mInv and m must not overlap.
      *
      * @param mInv the array that holds the output inverted matrix
      * @param mInvOffset an offset into mInv where the inverted matrix is
      *        stored.
      * @param m the input array
-     * @param mOffset an offset into m where the matrix is stored.
+     * @param mOffset an offset into m where the input matrix is stored.
      * @return true if the matrix could be inverted, false if it could not.
      */
     public static boolean invertM(float[] mInv, int mInvOffset, float[] m,
@@ -301,10 +308,11 @@
 
 
     /**
-     * Define a projection matrix in terms of six clip planes
-     * @param m the float array that holds the perspective matrix
+     * Defines a projection matrix in terms of six clip planes.
+     *
+     * @param m the float array that holds the output perspective matrix
      * @param offset the offset into float array m where the perspective
-     * matrix data is written
+     *        matrix data is written
      * @param left
      * @param right
      * @param bottom
@@ -358,11 +366,12 @@
     }
 
     /**
-     * Define a projection matrix in terms of a field of view angle, an
-     * aspect ratio, and z clip planes
+     * Defines a projection matrix in terms of a field of view angle, an
+     * aspect ratio, and z clip planes.
+     *
      * @param m the float array that holds the perspective matrix
      * @param offset the offset into float array m where the perspective
-     * matrix data is written
+     *        matrix data is written
      * @param fovy field of view in y direction, in degrees
      * @param aspect width to height aspect ratio of the viewport
      * @param zNear
@@ -395,7 +404,7 @@
     }
 
     /**
-     * Computes the length of a vector
+     * Computes the length of a vector.
      *
      * @param x x coordinate of a vector
      * @param y y coordinate of a vector
@@ -408,6 +417,7 @@
 
     /**
      * Sets matrix m to the identity matrix.
+     *
      * @param sm returns the result
      * @param smOffset index into sm where the result matrix starts
      */
@@ -421,7 +431,10 @@
     }
 
     /**
-     * Scales matrix  m by x, y, and z, putting the result in sm
+     * Scales matrix m by x, y, and z, putting the result in sm.
+     * <p>
+     * m and sm must not overlap.
+     *
      * @param sm returns the result
      * @param smOffset index into sm where the result matrix starts
      * @param m source matrix
@@ -444,7 +457,8 @@
     }
 
     /**
-     * Scales matrix m in place by sx, sy, and sz
+     * Scales matrix m in place by sx, sy, and sz.
+     *
      * @param m matrix to scale
      * @param mOffset index into m where the matrix starts
      * @param x scale factor x
@@ -462,7 +476,10 @@
     }
 
     /**
-     * Translates matrix m by x, y, and z, putting the result in tm
+     * Translates matrix m by x, y, and z, putting the result in tm.
+     * <p>
+     * m and tm must not overlap.
+     *
      * @param tm returns the result
      * @param tmOffset index into sm where the result matrix starts
      * @param m source matrix
@@ -487,6 +504,7 @@
 
     /**
      * Translates matrix m by x, y, and z in place.
+     *
      * @param m matrix
      * @param mOffset index into m where the matrix starts
      * @param x translation factor x
@@ -503,15 +521,18 @@
     }
 
     /**
-     * Rotates matrix m by angle a (in degrees) around the axis (x, y, z)
+     * Rotates matrix m by angle a (in degrees) around the axis (x, y, z).
+     * <p>
+     * m and rm must not overlap.
+     *
      * @param rm returns the result
      * @param rmOffset index into rm where the result matrix starts
      * @param m source matrix
      * @param mOffset index into m where the source matrix starts
      * @param a angle to rotate in degrees
-     * @param x scale factor x
-     * @param y scale factor y
-     * @param z scale factor z
+     * @param x X axis component
+     * @param y Y axis component
+     * @param z Z axis component
      */
     public static void rotateM(float[] rm, int rmOffset,
             float[] m, int mOffset,
@@ -524,13 +545,14 @@
 
     /**
      * Rotates matrix m in place by angle a (in degrees)
-     * around the axis (x, y, z)
+     * around the axis (x, y, z).
+     *
      * @param m source matrix
      * @param mOffset index into m where the matrix starts
      * @param a angle to rotate in degrees
-     * @param x scale factor x
-     * @param y scale factor y
-     * @param z scale factor z
+     * @param x X axis component
+     * @param y Y axis component
+     * @param z Z axis component
      */
     public static void rotateM(float[] m, int mOffset,
             float a, float x, float y, float z) {
@@ -542,13 +564,18 @@
     }
 
     /**
-     * Rotates matrix m by angle a (in degrees) around the axis (x, y, z)
+     * Creates a matrix for rotation by angle a (in degrees)
+     * around the axis (x, y, z).
+     * <p>
+     * An optimized path will be used for rotation about a major axis
+     * (e.g. x=1.0f y=0.0f z=0.0f).
+     *
      * @param rm returns the result
      * @param rmOffset index into rm where the result matrix starts
      * @param a angle to rotate in degrees
-     * @param x scale factor x
-     * @param y scale factor y
-     * @param z scale factor z
+     * @param x X axis component
+     * @param y Y axis component
+     * @param z Z axis component
      */
     public static void setRotateM(float[] rm, int rmOffset,
             float a, float x, float y, float z) {
@@ -608,7 +635,8 @@
     }
 
     /**
-     * Converts Euler angles to a rotation matrix
+     * Converts Euler angles to a rotation matrix.
+     *
      * @param rm returns the result
      * @param rmOffset index into rm where the result matrix starts
      * @param x angle of rotation, in degrees
@@ -651,7 +679,7 @@
     }
 
     /**
-     * Define a viewing transformation in terms of an eye point, a center of
+     * Defines a viewing transformation in terms of an eye point, a center of
      * view, and an up vector.
      *
      * @param rm returns the result
diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java
index 7537218..c81b00c 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintAdapter.java
@@ -20,7 +20,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.print.IPrintAdapter;
-import android.print.IPrintProgressListener;
+import android.print.IPrintResultCallback;
 import android.print.PageRange;
 import android.print.PrintAdapterInfo;
 import android.print.PrintAttributes;
@@ -50,7 +50,7 @@
 
     private final File mFile;
 
-    private final IPrintProgressListener mIPrintProgressListener;
+    private final IPrintResultCallback mIPrintProgressListener;
 
     private PrintAdapterInfo mInfo;
 
@@ -61,12 +61,12 @@
     public RemotePrintAdapter(IPrintAdapter printAdatper, File file) {
         mRemoteInterface = printAdatper;
         mFile = file;
-        mIPrintProgressListener = new IPrintProgressListener.Stub() {
+        mIPrintProgressListener = new IPrintResultCallback.Stub() {
             @Override
-            public void onWriteStarted(PrintAdapterInfo info,
+            public void onPrintStarted(PrintAdapterInfo info,
                     ICancellationSignal cancellationSignal) {
                 if (DEBUG) {
-                    Log.i(LOG_TAG, "IPrintProgressListener#onWriteStarted()");
+                    Log.i(LOG_TAG, "IPrintProgressListener#onPrintStarted()");
                 }
                 synchronized (mLock) {
                     mInfo = info;
@@ -75,9 +75,9 @@
             }
 
             @Override
-            public void onWriteFinished(List<PageRange> pages) {
+            public void onPrintFinished(List<PageRange> pages) {
                 if (DEBUG) {
-                    Log.i(LOG_TAG, "IPrintProgressListener#onWriteFinished(" + pages + ")");
+                    Log.i(LOG_TAG, "IPrintProgressListener#onPrintFinished(" + pages + ")");
                 }
                 synchronized (mLock) {
                     if (isPrintingLocked()) {
@@ -88,9 +88,9 @@
             }
 
             @Override
-            public void onWriteFailed(CharSequence error) {
+            public void onPrintFailed(CharSequence error) {
                 if (DEBUG) {
-                    Log.i(LOG_TAG, "IPrintProgressListener#onWriteFailed(" + error + ")");
+                    Log.i(LOG_TAG, "IPrintProgressListener#onPrintFailed(" + error + ")");
                 }
                 synchronized (mLock) {
                     if (isPrintingLocked()) {
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index a4fd7ad..4f30558 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2170,29 +2170,31 @@
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
             final ActivityRecord r = stack.topRunningActivityLocked(null);
+            final ActivityState state = r == null ? ActivityState.DESTROYED : r.state;
             if (isFrontStack(stack)) {
                 if (r == null) {
                     Slog.e(TAG, "validateTop...: null top activity, stack=" + stack);
                 } else {
-                    if (stack.mPausingActivity != null) {
+                    final ActivityRecord pausing = stack.mPausingActivity;
+                    if (pausing != null && pausing == r) {
                         Slog.e(TAG, "validateTop...: top stack has pausing activity r=" + r +
-                            " state=" + r.state);
+                            " state=" + state);
                     }
-                    if (r.state != ActivityState.INITIALIZING &&
-                            r.state != ActivityState.RESUMED) {
+                    if (state != ActivityState.INITIALIZING && state != ActivityState.RESUMED) {
                         Slog.e(TAG, "validateTop...: activity in front not resumed r=" + r +
-                                " state=" + r.state);
+                                " state=" + state);
                     }
                 }
             } else {
-                if (stack.mResumedActivity != null) {
+                final ActivityRecord resumed = stack.mResumedActivity;
+                if (resumed != null && resumed == r) {
                     Slog.e(TAG, "validateTop...: back stack has resumed activity r=" + r +
-                        " state=" + r.state);
+                        " state=" + state);
                 }
-                if (r != null && (r.state == ActivityState.INITIALIZING
-                        || r.state == ActivityState.RESUMED)) {
+                if (r != null && (state == ActivityState.INITIALIZING
+                        || state == ActivityState.RESUMED)) {
                     Slog.e(TAG, "validateTop...: activity in back resumed r=" + r +
-                            " state=" + r.state);
+                            " state=" + state);
                 }
             }
         }
diff --git a/services/java/com/android/server/wm/TaskStack.java b/services/java/com/android/server/wm/TaskStack.java
index 827958d..b43a7a1 100644
--- a/services/java/com/android/server/wm/TaskStack.java
+++ b/services/java/com/android/server/wm/TaskStack.java
@@ -16,7 +16,12 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
+import static com.android.server.wm.WindowManagerService.TAG;
+
 import android.graphics.Rect;
+import android.os.Debug;
+import android.util.Slog;
 import android.util.TypedValue;
 
 import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
@@ -88,6 +93,7 @@
      * @param toTop Whether to add it to the top or bottom.
      */
     boolean addTask(Task task, boolean toTop) {
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "addTask: task=" + task + " toTop=" + toTop);
         mStackBox.makeDirty();
         mTasks.add(toTop ? mTasks.size() : 0, task);
         task.mStack = this;
@@ -95,11 +101,14 @@
     }
 
     boolean moveTaskToTop(Task task) {
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToTop: task=" + task + " Callers="
+                + Debug.getCallers(6));
         mTasks.remove(task);
         return addTask(task, true);
     }
 
     boolean moveTaskToBottom(Task task) {
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToBottom: task=" + task);
         mTasks.remove(task);
         return addTask(task, false);
     }
@@ -110,6 +119,7 @@
      * @param task The Task to delete.
      */
     void removeTask(Task task) {
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "removeTask: task=" + task);
         mStackBox.makeDirty();
         mTasks.remove(task);
     }
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index fd06535..65ca00d 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -97,6 +97,7 @@
 import android.util.TypedValue;
 import android.view.Choreographer;
 import android.view.Display;
+import android.view.DisplayAdjustments;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IApplicationToken;
@@ -3776,7 +3777,6 @@
                 }
                 changed = mFocusedApp != newFocus;
                 mFocusedApp = newFocus;
-                moveTaskToTop(newFocus.groupId);
                 if (DEBUG_FOCUS) Slog.v(TAG, "Set focused app to: " + mFocusedApp
                         + " moveFocusNow=" + moveFocusNow);
                 if (changed) {
@@ -6609,8 +6609,9 @@
             displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
             displayInfo.appWidth = appWidth;
             displayInfo.appHeight = appHeight;
-            displayInfo.getLogicalMetrics(mRealDisplayMetrics, null);
-            displayInfo.getAppMetrics(mDisplayMetrics, null);
+            displayInfo.getLogicalMetrics(mRealDisplayMetrics,
+                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+            displayInfo.getAppMetrics(mDisplayMetrics);
             mDisplayManagerService.setDisplayInfoOverrideFromWindowManager(
                     displayContent.getDisplayId(), displayInfo);
         }
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 29d6e4d..44077ae 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -39,7 +39,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
-import android.view.CompatibilityInfoHolder;
+import android.view.DisplayAdjustments;
 import android.view.Display;
 
 import java.io.File;
@@ -574,7 +574,7 @@
 
     /** @hide */
     @Override
-    public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
+    public DisplayAdjustments getDisplayAdjustments(int displayId) {
         throw new UnsupportedOperationException();
     }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 21bef1c..98a32a5 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -67,8 +67,8 @@
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.BridgeInflater;
-import android.view.CompatibilityInfoHolder;
 import android.view.Display;
+import android.view.DisplayAdjustments;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -1394,7 +1394,7 @@
     }
 
     @Override
-    public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
+    public DisplayAdjustments getDisplayAdjustments(int displayId) {
         // pass
         return null;
     }