Merge "Fix issue #10948509: Crash in procstats when there is no data" into klp-dev
diff --git a/api/current.txt b/api/current.txt
index 4f398cf..37881e6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3972,7 +3972,6 @@
   }
 
   public static class Notification.Action implements android.os.Parcelable {
-    ctor public Notification.Action();
     ctor public Notification.Action(int, java.lang.CharSequence, android.app.PendingIntent);
     method public android.app.Notification.Action clone();
     method public int describeContents();
@@ -3988,7 +3987,6 @@
     ctor public Notification.BigPictureStyle(android.app.Notification.Builder);
     method public android.app.Notification.BigPictureStyle bigLargeIcon(android.graphics.Bitmap);
     method public android.app.Notification.BigPictureStyle bigPicture(android.graphics.Bitmap);
-    method public android.app.Notification build();
     method public android.app.Notification.BigPictureStyle setBigContentTitle(java.lang.CharSequence);
     method public android.app.Notification.BigPictureStyle setSummaryText(java.lang.CharSequence);
   }
@@ -3997,7 +3995,6 @@
     ctor public Notification.BigTextStyle();
     ctor public Notification.BigTextStyle(android.app.Notification.Builder);
     method public android.app.Notification.BigTextStyle bigText(java.lang.CharSequence);
-    method public android.app.Notification build();
     method public android.app.Notification.BigTextStyle setBigContentTitle(java.lang.CharSequence);
     method public android.app.Notification.BigTextStyle setSummaryText(java.lang.CharSequence);
   }
@@ -4042,14 +4039,13 @@
     ctor public Notification.InboxStyle();
     ctor public Notification.InboxStyle(android.app.Notification.Builder);
     method public android.app.Notification.InboxStyle addLine(java.lang.CharSequence);
-    method public android.app.Notification build();
     method public android.app.Notification.InboxStyle setBigContentTitle(java.lang.CharSequence);
     method public android.app.Notification.InboxStyle setSummaryText(java.lang.CharSequence);
   }
 
   public static abstract class Notification.Style {
     ctor public Notification.Style();
-    method public abstract android.app.Notification build();
+    method public android.app.Notification build();
     method protected void checkBuilder();
     method protected android.widget.RemoteViews getStandardView(int);
     method protected void internalSetBigContentTitle(java.lang.CharSequence);
@@ -10908,7 +10904,6 @@
     method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
     method public abstract void flush() throws android.hardware.camera2.CameraAccessException;
     method public abstract java.lang.String getId();
-    method public abstract android.hardware.camera2.CameraCharacteristics getProperties() throws android.hardware.camera2.CameraAccessException;
     method public abstract int setRepeatingBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract int setRepeatingRequest(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract void stopRepeating() throws android.hardware.camera2.CameraAccessException;
@@ -20993,7 +20988,6 @@
     field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type";
     field public static final java.lang.String COLUMN_SIZE = "_size";
     field public static final java.lang.String COLUMN_SUMMARY = "summary";
-    field public static final int FLAG_DIR_HIDE_GRID_TITLES = 64; // 0x40
     field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
     field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
     field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -21010,18 +21004,12 @@
     field public static final java.lang.String COLUMN_ICON = "icon";
     field public static final java.lang.String COLUMN_MIME_TYPES = "mime_types";
     field public static final java.lang.String COLUMN_ROOT_ID = "root_id";
-    field public static final java.lang.String COLUMN_ROOT_TYPE = "root_type";
     field public static final java.lang.String COLUMN_SUMMARY = "summary";
     field public static final java.lang.String COLUMN_TITLE = "title";
-    field public static final int FLAG_ADVANCED = 4; // 0x4
-    field public static final int FLAG_EMPTY = 32; // 0x20
     field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
     field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
-    field public static final int FLAG_SUPPORTS_RECENTS = 8; // 0x8
-    field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10
-    field public static final int ROOT_TYPE_DEVICE = 3; // 0x3
-    field public static final int ROOT_TYPE_SERVICE = 1; // 0x1
-    field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2
+    field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
+    field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
   }
 
   public abstract class DocumentsProvider extends android.content.ContentProvider {
@@ -31585,6 +31573,7 @@
     method public void setAnimationStyle(int);
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setContentWidth(int);
+    method public void setDropDownGravity(int);
     method public void setHeight(int);
     method public void setHorizontalOffset(int);
     method public void setInputMethodMode(int);
@@ -31766,6 +31755,7 @@
 
   public class PopupMenu {
     ctor public PopupMenu(android.content.Context, android.view.View);
+    ctor public PopupMenu(android.content.Context, android.view.View, int);
     method public void dismiss();
     method public android.view.View.OnTouchListener getDragToOpenListener();
     method public android.view.Menu getMenu();
@@ -31829,6 +31819,7 @@
     method public void setWindowLayoutMode(int, int);
     method public void showAsDropDown(android.view.View);
     method public void showAsDropDown(android.view.View, int, int);
+    method public void showAsDropDown(android.view.View, int, int, int);
     method public void showAtLocation(android.view.View, int, int, int);
     method public void update();
     method public void update(int, int);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e70ad1c..c63e586 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -432,28 +432,119 @@
 
     /**
      * Additional semantic data to be carried around with this Notification.
+     * <p>
+     * The extras keys defined here are intended to capture the original inputs to {@link Builder}
+     * APIs, and are intended to be used by
+     * {@link android.service.notification.NotificationListenerService} implementations to extract
+     * detailed information from notification objects.
      */
     public Bundle extras = new Bundle();
 
-    // extras keys for Builder inputs
+    /**
+     * {@link #extras} key: this is the title of the notification,
+     * as supplied to {@link Builder#setContentTitle(CharSequence)}.
+     */
     public static final String EXTRA_TITLE = "android.title";
+
+    /**
+     * {@link #extras} key: this is the title of the notification when shown in expanded form,
+     * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
+     */
     public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
+
+    /**
+     * {@link #extras} key: this is the main text payload, as supplied to
+     * {@link Builder#setContentText(CharSequence)}.
+     */
     public static final String EXTRA_TEXT = "android.text";
+
+    /**
+     * {@link #extras} key: this is a third line of text, as supplied to
+     * {@link Builder#setSubText(CharSequence)}.
+     */
     public static final String EXTRA_SUB_TEXT = "android.subText";
+
+    /**
+     * {@link #extras} key: this is a small piece of additional text as supplied to
+     * {@link Builder#setContentInfo(CharSequence)}.
+     */
     public static final String EXTRA_INFO_TEXT = "android.infoText";
+
+    /**
+     * {@link #extras} key: this is a line of summary information intended to be shown
+     * alongside expanded notifications, as supplied to (e.g.)
+     * {@link BigTextStyle#setSummaryText(CharSequence)}.
+     */
     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
+
+    /**
+     * {@link #extras} key: this is the resource ID of the notification's main small icon, as
+     * supplied to {@link Builder#setSmallIcon(int)}.
+     */
     public static final String EXTRA_SMALL_ICON = "android.icon";
+
+    /**
+     * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
+     * notification payload, as
+     * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
+     */
     public static final String EXTRA_LARGE_ICON = "android.largeIcon";
+
+    /**
+     * {@link #extras} key: this is a bitmap to be used instead of the one from
+     * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
+     * shown in its expanded form, as supplied to
+     * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
+     */
     public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
+
+    /**
+     * {@link #extras} key: this is the progress value supplied to
+     * {@link Builder#setProgress(int, int, boolean)}.
+     */
     public static final String EXTRA_PROGRESS = "android.progress";
+
+    /**
+     * {@link #extras} key: this is the maximum value supplied to
+     * {@link Builder#setProgress(int, int, boolean)}.
+     */
     public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
+
+    /**
+     * {@link #extras} key: whether the progress bar is indeterminate, supplied to
+     * {@link Builder#setProgress(int, int, boolean)}.
+     */
     public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
+
+    /**
+     * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
+     * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
+     * {@link Builder#setUsesChronometer(boolean)}.
+     */
     public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
+
+    /**
+     * {@link #extras} key: whether {@link #when} should be shown,
+     * as supplied to {@link Builder#setShowWhen(boolean)}.
+     */
     public static final String EXTRA_SHOW_WHEN = "android.showWhen";
+
+    /**
+     * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
+     * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
+     */
     public static final String EXTRA_PICTURE = "android.picture";
+
+    /**
+     * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
+     * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
+     */
     public static final String EXTRA_TEXT_LINES = "android.textLines";
 
-    // extras keys for other interesting pieces of information
+    /**
+     * {@link #extras} key: An array of people that this notification relates to, specified
+     * by contacts provider contact URI.
+     */
     public static final String EXTRA_PEOPLE = "android.people";
 
     /**
@@ -464,38 +555,53 @@
     public static final String EXTRA_SCORE_MODIFIED = "android.scoreModified";
 
     /**
-     * Notification extra to specify heads up display preference.
+     * Not used.
      * @hide
      */
     public static final String EXTRA_AS_HEADS_UP = "headsup";
 
     /**
-     * Value for {@link #EXTRA_AS_HEADS_UP} indicating that heads up display is not appropriate.
+     * Value for {@link #EXTRA_AS_HEADS_UP}.
      * @hide
      */
     public static final int HEADS_UP_NEVER = 0;
 
     /**
-     * Default value for {@link #EXTRA_AS_HEADS_UP} indicating that heads up display is appropriate.
+     * Default value for {@link #EXTRA_AS_HEADS_UP}.
      * @hide
      */
     public static final int HEADS_UP_ALLOWED = 1;
 
     /**
-     * Value for {@link #EXTRA_AS_HEADS_UP} that advocates for heads up display.
+     * Value for {@link #EXTRA_AS_HEADS_UP}.
      * @hide
      */
     public static final int HEADS_UP_REQUESTED = 2;
 
     /**
-     * Structure to encapsulate an "action", including title and icon, that can be attached to a Notification.
+     * Structure to encapsulate a named action that can be shown as part of this notification.
+     * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
+     * selected by the user.
+     * <p>
+     * Apps should use {@link Builder#addAction(int, CharSequence, PendingIntent)} to create and
+     * attach actions.
      */
     public static class Action implements Parcelable {
+        /**
+         * Small icon representing the action.
+         */
         public int icon;
+        /**
+         * Title of the action.
+         */
         public CharSequence title;
+        /**
+         * Intent to send when the user invokes this action. May be null, in which case the action
+         * may be rendered in a disabled presentation by the system UI.
+         */
         public PendingIntent actionIntent;
-        @SuppressWarnings("unused")
-        public Action() { }
+ 
+        private Action() { }
         private Action(Parcel in) {
             icon = in.readInt();
             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -503,16 +609,20 @@
                 actionIntent = PendingIntent.CREATOR.createFromParcel(in);
             }
         }
-        public Action(int icon_, CharSequence title_, PendingIntent intent_) {
-            this.icon = icon_;
-            this.title = title_;
-            this.actionIntent = intent_;
+        /**
+         * Use {@link Builder#addAction(int, CharSequence, PendingIntent)}.
+         */
+        public Action(int icon, CharSequence title, PendingIntent intent) {
+            this.icon = icon;
+            this.title = title;
+            this.actionIntent = intent;
         }
+
         @Override
         public Action clone() {
             return new Action(
                 this.icon,
-                this.title.toString(),
+                this.title,
                 this.actionIntent // safe to alias
             );
         }
@@ -542,6 +652,12 @@
         };
     }
 
+    /**
+     * Array of all {@link Action} structures attached to this notification by
+     * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
+     * {@link android.service.notification.NotificationListenerService} that provide an alternative
+     * interface for invoking actions.
+     */
     public Action[] actions;
 
     /**
@@ -1468,8 +1584,15 @@
         /**
          * Add an action to this notification. Actions are typically displayed by
          * the system as a button adjacent to the notification content.
-         * <br>
-         * A notification displays up to 3 actions, from left to right in the order they were added.
+         * <p>
+         * Every action must have an icon (32dp square and matching the
+         * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
+         * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
+         * <p>
+         * A notification in its expanded form can display up to 3 actions, from left to right in
+         * the order they were added. Actions will not be displayed when the notification is
+         * collapsed, however, so be sure that any essential functions may be accessed by the user
+         * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
          *
          * @param icon Resource ID of a drawable that represents the action.
          * @param title Text describing the action.
@@ -1666,8 +1789,9 @@
 
         /**
          * Apply the unstyled operations and return a new {@link Notification} object.
+         * @hide
          */
-        private Notification buildUnstyled() {
+        public Notification buildUnstyled() {
             Notification n = new Notification();
             n.when = mWhen;
             n.icon = mSmallIcon;
@@ -1745,12 +1869,10 @@
          * object.
          */
         public Notification build() {
-            final Notification n;
+            Notification n = buildUnstyled();
 
             if (mStyle != null) {
-                n = mStyle.build();
-            } else {
-                n = buildUnstyled();
+                n = mStyle.buildStyled(n);
             }
 
             n.extras = mExtras != null ? new Bundle(mExtras) : new Bundle();
@@ -1860,7 +1982,21 @@
             }
         }
 
-        public abstract Notification build();
+        /**
+         * @hide
+         */
+        public abstract Notification buildStyled(Notification wip);
+
+        /**
+         * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
+         * attached to.
+         *
+         * @return the fully constructed Notification.
+         */
+        public Notification build() {
+            checkBuilder();
+            return mBuilder.build();
+        }
     }
 
     /**
@@ -1946,10 +2082,11 @@
             extras.putParcelable(EXTRA_PICTURE, mPicture);
         }
 
+        /**
+         * @hide
+         */
         @Override
-        public Notification build() {
-            checkBuilder();
-            Notification wip = mBuilder.buildUnstyled();
+        public Notification buildStyled(Notification wip) {
             if (mBigLargeIconSet ) {
                 mBuilder.mLargeIcon = mBigLargeIcon;
             }
@@ -2039,10 +2176,11 @@
             return contentView;
         }
 
+        /**
+         * @hide
+         */
         @Override
-        public Notification build() {
-            checkBuilder();
-            Notification wip = mBuilder.buildUnstyled();
+        public Notification buildStyled(Notification wip) {
             wip.bigContentView = makeBigContentView();
 
             wip.extras.putCharSequence(EXTRA_TEXT, mBigText);
@@ -2150,10 +2288,11 @@
             return contentView;
         }
 
+        /**
+         * @hide
+         */
         @Override
-        public Notification build() {
-            checkBuilder();
-            Notification wip = mBuilder.buildUnstyled();
+        public Notification buildStyled(Notification wip) {
             wip.bigContentView = makeBigContentView();
 
             return wip;
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index ec89041..a9a72b0 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,11 +16,9 @@
 
 package android.hardware.camera2;
 
-import android.view.Surface;
 import android.os.Handler;
-import android.util.Log;
+import android.view.Surface;
 
-import java.lang.AutoCloseable;
 import java.util.List;
 
 /**
@@ -127,24 +125,11 @@
      * @return the ID for this camera device
      *
      * @see CameraManager#getCameraCharacteristics
-     * @see CameraManager#getDeviceIdList
+     * @see CameraManager#getCameraIdList
      */
     public String getId();
 
     /**
-     * Get the static properties for this camera. These are identical to the
-     * properties returned by {@link CameraManager#getCameraCharacteristics}.
-     *
-     * @return the static properties of the camera
-     *
-     * @throws CameraAccessException if the camera device is no longer connected or has
-     *                               encountered a fatal error
-     * @throws IllegalStateException if the camera device has been closed
-     *
-     * @see CameraManager#getCameraCharacteristics
-     */
-    public CameraCharacteristics getProperties() throws CameraAccessException;
-    /**
      * <p>Set up a new output set of Surfaces for the camera device.</p>
      *
      * <p>The configuration determines the set of potential output Surfaces for
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index f126472..70a6f44 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -88,24 +88,6 @@
     }
 
     @Override
-    public CameraCharacteristics getProperties() throws CameraAccessException {
-
-        CameraMetadataNative info = new CameraMetadataNative();
-
-        try {
-            mRemoteDevice.getCameraInfo(/*out*/info);
-        } catch(CameraRuntimeException e) {
-            throw e.asChecked();
-        } catch(RemoteException e) {
-            // impossible
-            return null;
-        }
-
-        CameraCharacteristics properties = new CameraCharacteristics(info);
-        return properties;
-    }
-
-    @Override
     public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
         synchronized (mLock) {
             HashSet<Surface> addSet = new HashSet<Surface>(outputs);    // Streams to create
diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl
index 4044b31..3bd515b 100644
--- a/core/java/android/print/IPrintManager.aidl
+++ b/core/java/android/print/IPrintManager.aidl
@@ -45,6 +45,7 @@
     void removePrintJobStateChangeListener(in IPrintJobStateChangeListener listener,
             int userId);
 
+    List<PrintServiceInfo> getInstalledPrintServices(int userId);
     List<PrintServiceInfo> getEnabledPrintServices(int userId);
 
     void createPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, int userId);
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index 291e81f..96b168d 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -36,7 +36,6 @@
  */
 oneway interface IPrintSpooler {
     void removeObsoletePrintJobs();
-    void forgetPrintJobs(in List<PrintJobId> printJob);
     void getPrintJobInfos(IPrintSpoolerCallbacks callback, in ComponentName componentName,
             int state, int appId, int sequence);
     void getPrintJobInfo(in PrintJobId printJobId, IPrintSpoolerCallbacks callback,
diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl
index 0cf00cc..8270812 100644
--- a/core/java/android/print/IPrintSpoolerClient.aidl
+++ b/core/java/android/print/IPrintSpoolerClient.aidl
@@ -29,5 +29,5 @@
     void onPrintJobQueued(in PrintJobInfo printJob);
     void onAllPrintJobsForServiceHandled(in ComponentName printService);
     void onAllPrintJobsHandled();
-    void onPrintJobStateChanged(in PrintJobId printJobId, int appId);
+    void onPrintJobStateChanged(in PrintJobInfo printJob);
 }
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index a015388..0859fdd 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -289,7 +289,26 @@
                 return enabledServices;
             }
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error getting the enalbed print services", re);
+            Log.e(LOG_TAG, "Error getting the enabled print services", re);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Gets the list of installed print services.
+     *
+     * @return The installed service list or an empty list.
+     *
+     * @hide
+     */
+    public List<PrintServiceInfo> getInstalledPrintServices() {
+        try {
+            List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId);
+            if (installedServices != null) {
+                return installedServices;
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error getting the installed print services", re);
         }
         return Collections.emptyList();
     }
diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java
index c6dbc16..d32b71b 100644
--- a/core/java/android/print/PrinterDiscoverySession.java
+++ b/core/java/android/print/PrinterDiscoverySession.java
@@ -28,6 +28,7 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 
 /**
@@ -40,8 +41,8 @@
     private static final int MSG_PRINTERS_ADDED = 1;
     private static final int MSG_PRINTERS_REMOVED = 2;
 
-    private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
-            new ArrayMap<PrinterId, PrinterInfo>();
+    private final LinkedHashMap<PrinterId, PrinterInfo> mPrinters =
+            new LinkedHashMap<PrinterId, PrinterInfo>();
 
     private final IPrintManager mPrintManager;
 
@@ -192,22 +193,44 @@
         }
     }
 
-    private void handlePrintersAdded(List<PrinterInfo> printers) {
+    private void handlePrintersAdded(List<PrinterInfo> addedPrinters) {
         if (isDestroyed()) {
             return;
         }
-        boolean printersChanged = false;
-        final int addedPrinterCount = printers.size();
-        for (int i = 0; i < addedPrinterCount; i++) {
-            PrinterInfo addedPrinter = printers.get(i);
-            PrinterInfo oldPrinter = mPrinters.put(addedPrinter.getId(), addedPrinter);
-            if (oldPrinter == null || !oldPrinter.equals(addedPrinter)) {
-                printersChanged = true;
+
+        // No old printers - do not bother keeping their position.
+        if (mPrinters.isEmpty()) {
+            final int printerCount = addedPrinters.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterInfo printer = addedPrinters.get(i);
+                mPrinters.put(printer.getId(), printer);
+            }
+            notifyOnPrintersChanged();
+            return;
+        }
+
+        // Add the printers to a map.
+        ArrayMap<PrinterId, PrinterInfo> addedPrintersMap =
+                new ArrayMap<PrinterId, PrinterInfo>();
+        final int printerCount = addedPrinters.size();
+        for (int i = 0; i < printerCount; i++) {
+            PrinterInfo printer = addedPrinters.get(i);
+            addedPrintersMap.put(printer.getId(), printer);
+        }
+
+        // Update printers we already have.
+        for (PrinterId oldPrinterId : mPrinters.keySet()) {
+            PrinterInfo updatedPrinter = addedPrintersMap.remove(oldPrinterId);
+            if (updatedPrinter != null) {
+                mPrinters.put(oldPrinterId, updatedPrinter);
             }
         }
-        if (printersChanged) {
-            notifyOnPrintersChanged();
-        }
+
+        // Add the new printers, i.e. what is left.
+        mPrinters.putAll(addedPrintersMap);
+
+        // Announce the change.
+        notifyOnPrintersChanged();
     }
 
     private void handlePrintersRemoved(List<PrinterId> printerIds) {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 4c9af19..85ec803 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -99,7 +99,7 @@
         /**
          * Unique ID of a document. This ID is both provided by and interpreted
          * by a {@link DocumentsProvider}, and should be treated as an opaque
-         * value by client applications.
+         * value by client applications. This column is required.
          * <p>
          * Each document must have a unique ID within a provider, but that
          * single document may be included as a child of multiple directories.
@@ -117,7 +117,7 @@
          * Concrete MIME type of a document. For example, "image/png" or
          * "application/pdf" for openable files. A document can also be a
          * directory containing additional documents, which is represented with
-         * the {@link #MIME_TYPE_DIR} MIME type.
+         * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
          * <p>
          * Type: STRING
          *
@@ -127,15 +127,15 @@
 
         /**
          * Display name of a document, used as the primary title displayed to a
-         * user.
+         * user. This column is required.
          * <p>
          * Type: STRING
          */
         public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
 
         /**
-         * Summary of a document, which may be shown to a user. The summary may
-         * be {@code null}.
+         * Summary of a document, which may be shown to a user. This column is
+         * optional, and may be {@code null}.
          * <p>
          * Type: STRING
          */
@@ -143,9 +143,9 @@
 
         /**
          * Timestamp when a document was last modified, in milliseconds since
-         * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
-         * {@link DocumentsProvider} can update this field using events from
-         * {@link OnCloseListener} or other reliable
+         * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
+         * {@code null} if unknown. A {@link DocumentsProvider} can update this
+         * field using events from {@link OnCloseListener} or other reliable
          * {@link ParcelFileDescriptor} transports.
          * <p>
          * Type: INTEGER (long)
@@ -155,15 +155,16 @@
         public static final String COLUMN_LAST_MODIFIED = "last_modified";
 
         /**
-         * Specific icon resource ID for a document, or {@code null} to use
-         * platform default icon based on {@link #COLUMN_MIME_TYPE}.
+         * Specific icon resource ID for a document. This column is optional,
+         * and may be {@code null} to use a platform-provided default icon based
+         * on {@link #COLUMN_MIME_TYPE}.
          * <p>
          * Type: INTEGER (int)
          */
         public static final String COLUMN_ICON = "icon";
 
         /**
-         * Flags that apply to a document.
+         * Flags that apply to a document. This column is required.
          * <p>
          * Type: INTEGER (int)
          *
@@ -171,12 +172,13 @@
          * @see #FLAG_SUPPORTS_DELETE
          * @see #FLAG_SUPPORTS_THUMBNAIL
          * @see #FLAG_DIR_PREFERS_GRID
-         * @see #FLAG_DIR_SUPPORTS_CREATE
+         * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
          */
         public static final String COLUMN_FLAGS = "flags";
 
         /**
-         * Size of a document, in bytes, or {@code null} if unknown.
+         * Size of a document, in bytes, or {@code null} if unknown. This column
+         * is required.
          * <p>
          * Type: INTEGER (long)
          */
@@ -211,7 +213,7 @@
          * writability of a document may change over time, for example due to
          * remote access changes. This flag indicates that a document client can
          * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
-         *
+         * 
          * @see #COLUMN_FLAGS
          */
         public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
@@ -265,8 +267,9 @@
          *
          * @see #COLUMN_FLAGS
          * @see #FLAG_DIR_PREFERS_GRID
+         * @hide
          */
-        public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 6;
+        public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 16;
     }
 
     /**
@@ -282,31 +285,17 @@
         /**
          * Unique ID of a root. This ID is both provided by and interpreted by a
          * {@link DocumentsProvider}, and should be treated as an opaque value
-         * by client applications.
+         * by client applications. This column is required.
          * <p>
          * Type: STRING
          */
         public static final String COLUMN_ROOT_ID = "root_id";
 
         /**
-         * Type of a root, used for clustering when presenting multiple roots to
-         * a user.
+         * Flags that apply to a root. This column is required.
          * <p>
          * Type: INTEGER (int)
          *
-         * @see #ROOT_TYPE_SERVICE
-         * @see #ROOT_TYPE_SHORTCUT
-         * @see #ROOT_TYPE_DEVICE
-         */
-        public static final String COLUMN_ROOT_TYPE = "root_type";
-
-        /**
-         * Flags that apply to a root.
-         * <p>
-         * Type: INTEGER (int)
-         *
-         * @see #FLAG_ADVANCED
-         * @see #FLAG_EMPTY
          * @see #FLAG_LOCAL_ONLY
          * @see #FLAG_SUPPORTS_CREATE
          * @see #FLAG_SUPPORTS_RECENTS
@@ -315,22 +304,23 @@
         public static final String COLUMN_FLAGS = "flags";
 
         /**
-         * Icon resource ID for a root.
+         * Icon resource ID for a root. This column is required.
          * <p>
          * Type: INTEGER (int)
          */
         public static final String COLUMN_ICON = "icon";
 
         /**
-         * Title for a root, which will be shown to a user.
+         * Title for a root, which will be shown to a user. This column is
+         * required.
          * <p>
          * Type: STRING
          */
         public static final String COLUMN_TITLE = "title";
 
         /**
-         * Summary for this root, which may be shown to a user. The summary may
-         * be {@code null}.
+         * Summary for this root, which may be shown to a user. This column is
+         * optional, and may be {@code null}.
          * <p>
          * Type: STRING
          */
@@ -338,7 +328,7 @@
 
         /**
          * Document which is a directory that represents the top directory of
-         * this root.
+         * this root. This column is required.
          * <p>
          * Type: STRING
          *
@@ -347,20 +337,20 @@
         public static final String COLUMN_DOCUMENT_ID = "document_id";
 
         /**
-         * Number of bytes available in this root, or {@code null} if unknown or
-         * unbounded.
+         * Number of bytes available in this root. This column is optional, and
+         * may be {@code null} if unknown or unbounded.
          * <p>
          * Type: INTEGER (long)
          */
         public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
 
         /**
-         * MIME types supported by this root, or {@code null} if the root
-         * supports all MIME types. Multiple MIME types can be separated by a
-         * newline. For example, a root supporting audio might use
-         * "audio/*\napplication/x-flac".
+         * MIME types supported by this root. This column is optional, and if
+         * {@code null} the root is assumed to support all MIME types. Multiple
+         * MIME types can be separated by a newline. For example, a root
+         * supporting audio might return "audio/*\napplication/x-flac".
          * <p>
-         * Type: String
+         * Type: STRING
          */
         public static final String COLUMN_MIME_TYPES = "mime_types";
 
@@ -368,29 +358,6 @@
         public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
 
         /**
-         * Type of root that represents a storage service, such as a cloud-based
-         * service.
-         *
-         * @see #COLUMN_ROOT_TYPE
-         */
-        public static final int ROOT_TYPE_SERVICE = 1;
-
-        /**
-         * Type of root that represents a shortcut to content that may be
-         * available elsewhere through another storage root.
-         *
-         * @see #COLUMN_ROOT_TYPE
-         */
-        public static final int ROOT_TYPE_SHORTCUT = 2;
-
-        /**
-         * Type of root that represents a physical storage device.
-         *
-         * @see #COLUMN_ROOT_TYPE
-         */
-        public static final int ROOT_TYPE_DEVICE = 3;
-
-        /**
          * Flag indicating that at least one directory under this root supports
          * creating content. Roots with this flag will be shown when an
          * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
@@ -409,21 +376,13 @@
         public static final int FLAG_LOCAL_ONLY = 1 << 1;
 
         /**
-         * Flag indicating that this root should only be visible to advanced
-         * users.
-         *
-         * @see #COLUMN_FLAGS
-         */
-        public static final int FLAG_ADVANCED = 1 << 2;
-
-        /**
          * Flag indicating that this root can report recently modified
          * documents.
          *
          * @see #COLUMN_FLAGS
          * @see DocumentsContract#buildRecentDocumentsUri(String, String)
          */
-        public static final int FLAG_SUPPORTS_RECENTS = 1 << 3;
+        public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
 
         /**
          * Flag indicating that this root supports search.
@@ -432,19 +391,31 @@
          * @see DocumentsProvider#querySearchDocuments(String, String,
          *      String[])
          */
-        public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
+        public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
 
         /**
          * Flag indicating that this root is currently empty. This may be used
          * to hide the root when opening documents, but the root will still be
          * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
-         * also set.
+         * also set. If the value of this flag changes, such as when a root
+         * becomes non-empty, you must send a content changed notification for
+         * {@link DocumentsContract#buildRootsUri(String)}.
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsProvider#querySearchDocuments(String, String,
-         *      String[])
+         * @see ContentResolver#notifyChange(Uri,
+         *      android.database.ContentObserver, boolean)
+         * @hide
          */
-        public static final int FLAG_EMPTY = 1 << 5;
+        public static final int FLAG_EMPTY = 1 << 16;
+
+        /**
+         * Flag indicating that this root should only be visible to advanced
+         * users.
+         *
+         * @see #COLUMN_FLAGS
+         * @hide
+         */
+        public static final int FLAG_ADVANCED = 1 << 17;
     }
 
     /**
diff --git a/core/java/android/transition/TextChange.java b/core/java/android/transition/ChangeText.java
similarity index 84%
rename from core/java/android/transition/TextChange.java
rename to core/java/android/transition/ChangeText.java
index cf190a1..b1be70f 100644
--- a/core/java/android/transition/TextChange.java
+++ b/core/java/android/transition/ChangeText.java
@@ -37,7 +37,7 @@
  *
  * @hide
  */
-public class TextChange extends Transition {
+public class ChangeText extends Transition {
 
     private static final String LOG_TAG = "TextChange";
 
@@ -103,7 +103,7 @@
      * transition is run.
      * @return this textChange object.
      */
-    public TextChange setChangeBehavior(int changeBehavior) {
+    public ChangeText setChangeBehavior(int changeBehavior) {
         if (changeBehavior >= CHANGE_BEHAVIOR_KEEP && changeBehavior <= CHANGE_BEHAVIOR_OUT_IN) {
             mChangeBehavior = changeBehavior;
         }
@@ -179,9 +179,13 @@
             startSelectionStart = startSelectionEnd = endSelectionStart = endSelectionEnd = -1;
         }
         if (!startText.equals(endText)) {
-            view.setText(startText);
-            if (view instanceof EditText) {
-                setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
+            final int startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR);
+            final int endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR);
+            if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
+                view.setText(startText);
+                if (view instanceof EditText) {
+                    setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
+                }
             }
             Animator anim;
             if (mChangeBehavior == CHANGE_BEHAVIOR_KEEP) {
@@ -200,8 +204,6 @@
                 });
             } else {
                 // Fade out start text
-                final int startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR);
-                final int endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR);
                 ValueAnimator outAnim = null, inAnim = null;
                 if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN ||
                         mChangeBehavior == CHANGE_BEHAVIOR_OUT) {
@@ -210,8 +212,8 @@
                         @Override
                         public void onAnimationUpdate(ValueAnimator animation) {
                             int currAlpha = (Integer) animation.getAnimatedValue();
-                            view.setTextColor(currAlpha << 24 | Color.red(startColor) << 16 |
-                                    Color.green(startColor) << 8 | Color.red(startColor));
+                            view.setTextColor(currAlpha << 24 | startColor & 0xff0000 |
+                                    startColor & 0xff00 | startColor & 0xff);
                         }
                     });
                     outAnim.addListener(new AnimatorListenerAdapter() {
@@ -225,6 +227,8 @@
                                             endSelectionEnd);
                                 }
                             }
+                            // restore opaque alpha and correct end color
+                            view.setTextColor(endColor);
                         }
                     });
                 }
@@ -239,6 +243,13 @@
                                     Color.green(endColor) << 8 | Color.red(endColor));
                         }
                     });
+                    inAnim.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationCancel(Animator animation) {
+                            // restore opaque alpha and correct end color
+                            view.setTextColor(endColor);
+                        }
+                    });
                 }
                 if (outAnim != null && inAnim != null) {
                     anim = new AnimatorSet();
@@ -251,21 +262,32 @@
                 }
             }
             TransitionListener transitionListener = new TransitionListenerAdapter() {
-                boolean mCanceled = false;
+                int mPausedColor = 0;
 
                 @Override
                 public void onTransitionPause(Transition transition) {
-                    view.setText(endText);
-                    if (view instanceof EditText) {
-                        setSelection(((EditText) view), endSelectionStart, endSelectionEnd);
+                    if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
+                        view.setText(endText);
+                        if (view instanceof EditText) {
+                            setSelection(((EditText) view), endSelectionStart, endSelectionEnd);
+                        }
+                    }
+                    if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
+                        mPausedColor = view.getCurrentTextColor();
+                        view.setTextColor(endColor);
                     }
                 }
 
                 @Override
                 public void onTransitionResume(Transition transition) {
-                    view.setText(startText);
-                    if (view instanceof EditText) {
-                        setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
+                    if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
+                        view.setText(startText);
+                        if (view instanceof EditText) {
+                            setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
+                        }
+                    }
+                    if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
+                        view.setTextColor(mPausedColor);
                     }
                 }
             };
diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java
index 5f948bd..8edb1ff 100644
--- a/core/java/android/transition/Fade.java
+++ b/core/java/android/transition/Fade.java
@@ -30,6 +30,24 @@
  * {@link View#setVisibility(int)} state of the view as well as whether it
  * is parented in the current view hierarchy.
  *
+ * <p>The ability of this transition to fade out a particular view, and the
+ * way that that fading operation takes place, is based on
+ * the situation of the view in the view hierarchy. For example, if a view was
+ * simply removed from its parent, then the view will be added into a {@link
+ * android.view.ViewGroupOverlay} while fading. If a visible view is
+ * changed to be {@link View#GONE} or {@link View#INVISIBLE}, then the
+ * visibility will be changed to {@link View#VISIBLE} for the duration of
+ * the animation. However, if a view is in a hierarchy which is also altering
+ * its visibility, the situation can be more complicated. In general, if a
+ * view that is no longer in the hierarchy in the end scene still has a
+ * parent (so its parent hierarchy was removed, but it was not removed from
+ * its parent), then it will be left alone to avoid side-effects from
+ * improperly removing it from its parent. The only exception to this is if
+ * the previous {@link Scene} was
+ * {@link Scene#getSceneForLayout(android.view.ViewGroup, int, android.content.Context)
+ * created from a layout resource file}, then it is considered safe to un-parent
+ * the starting scene view in order to fade it out.</p>
+ *
  * <p>A Fade transition can be described in a resource file by using the
  * tag <code>fade</code>, along with the standard
  * attributes of {@link android.R.styleable#Fade} and
@@ -167,7 +185,7 @@
         if ((mFadingMode & OUT) != OUT) {
             return null;
         }
-        View view;
+        View view = null;
         View startView = (startValues != null) ? startValues.view : null;
         View endView = (endValues != null) ? endValues.view : null;
         if (DBG) {
@@ -177,9 +195,28 @@
         View overlayView = null;
         View viewToKeep = null;
         if (endView == null || endView.getParent() == null) {
-            // view was removed: add the start view to the Overlay
-            view = startView;
-            overlayView = view;
+            if (endView != null) {
+                // endView was removed from its parent - add it to the overlay
+                view = overlayView = endView;
+            } else if (startView != null) {
+                // endView does not exist. Use startView only under certain
+                // conditions, because placing a view in an overlay necessitates
+                // it being removed from its current parent
+                if (startView.getParent() == null) {
+                    // no parent - safe to use
+                    view = overlayView = startView;
+                } else if (startView.getParent() instanceof View &&
+                        startView.getParent().getParent() == null) {
+                    View startParent = (View) startView.getParent();
+                    int id = startParent.getId();
+                    if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
+                        // no parent, but its parent is unparented  but the parent
+                        // hierarchy has been replaced by a new hierarchy with the same id
+                        // and it is safe to un-parent startView
+                        view = overlayView = startView;
+                    }
+                }
+            }
         } else {
             // visibility change
             if (endVisibility == View.INVISIBLE) {
diff --git a/core/java/android/transition/Scene.java b/core/java/android/transition/Scene.java
index f81eeef..d798abe 100644
--- a/core/java/android/transition/Scene.java
+++ b/core/java/android/transition/Scene.java
@@ -157,11 +157,11 @@
     public void enter() {
 
         // Apply layout change, if any
-        if (mLayoutId >= 0 || mLayout != null) {
+        if (mLayoutId > 0 || mLayout != null) {
             // empty out parent container before adding to it
             getSceneRoot().removeAllViews();
 
-            if (mLayoutId >= 0) {
+            if (mLayoutId > 0) {
                 LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
             } else {
                 mSceneRoot.addView(mLayout);
@@ -242,4 +242,19 @@
         mExitAction = action;
     }
 
+
+    /**
+     * Returns whether this Scene was created by a layout resource file, determined
+     * by the layoutId passed into
+     * {@link #getSceneForLayout(android.view.ViewGroup, int, android.content.Context)}.
+     * This is called by TransitionManager to determine whether it is safe for views from
+     * this scene to be removed from their parents when the scene is exited, which is
+     * used by {@link Fade} to fade these views out (the views must be removed from
+     * their parent in order to add them to the overlay for fading purposes). If a
+     * Scene is not based on a resource file, then the impact of removing views
+     * arbitrarily is unknown and should be avoided.
+     */
+    boolean isCreatedFromLayoutResource() {
+        return (mLayoutId > 0);
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index a552fd4..dcf668b 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -118,6 +118,14 @@
     // Scene Root is set at createAnimator() time in the cloned Transition
     ViewGroup mSceneRoot = null;
 
+    // Whether removing views from their parent is possible. This is only for views
+    // in the start scene, which are no longer in the view hierarchy. This property
+    // is determined by whether the previous Scene was created from a layout
+    // resource, and thus the views from the exited scene are going away anyway
+    // and can be removed as necessary to achieve a particular effect, such as
+    // removing them from parents to add them to overlays.
+    boolean mCanRemoveViews = false;
+
     // Track all animators in use in case the transition gets canceled and needs to
     // cancel running animators
     private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>();
@@ -1445,6 +1453,10 @@
         return this;
     }
 
+    void setCanRemoveViews(boolean canRemoveViews) {
+        mCanRemoveViews = canRemoveViews;
+    }
+
     @Override
     public String toString() {
         return toString("");
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 44ca4e5..9be91d0 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -178,6 +178,11 @@
         Transition transitionClone = transition.clone();
         transitionClone.setSceneRoot(sceneRoot);
 
+        Scene oldScene = Scene.getCurrentScene(sceneRoot);
+        if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
+            transitionClone.setCanRemoveViews(true);
+        }
+
         sceneChangeSetup(sceneRoot, transitionClone);
 
         scene.enter();
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 6fdd309..79cd8b6 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -340,6 +340,15 @@
     }
 
     @Override
+    void setCanRemoveViews(boolean canRemoveViews) {
+        super.setCanRemoveViews(canRemoveViews);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).setCanRemoveViews(canRemoveViews);
+        }
+    }
+
+    @Override
     String toString(String indent) {
         String result = super.toString(indent);
         for (int i = 0; i < mTransitions.size(); ++i) {
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index f49821f..44f92cd 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -30,22 +30,6 @@
  * changes occur. Subclasses should implement one or both of the methods
  * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)},
  * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)},
- *
- * <p>Note that a view's visibility change is determined by both whether the view
- * itself is changing and whether its parent hierarchy's visibility is changing.
- * That is, a view that appears in the end scene will only trigger a call to
- * {@link #onAppear(android.view.ViewGroup, TransitionValues, int, TransitionValues, int)
- * appear()} if its parent hierarchy was stable between the start and end scenes.
- * This is done to avoid causing a visibility transition on every node in a hierarchy
- * when only the top-most node is the one that should be transitioned in/out.
- * Stability is determined by either the parent hierarchy views being the same
- * between scenes or, if scenes are inflated from layout resource files and thus
- * have result in different view instances, if the views represented by
- * the ids of those parents are stable. This means that visibility determination
- * is more effective with inflated view hierarchies if ids are used.
- * The exception to this is when the visibility subclass transition is
- * targeted at specific views, in which case the visibility of parent views
- * is ignored.</p>
  */
 public abstract class Visibility extends Transition {
 
@@ -111,51 +95,6 @@
         return visibility == View.VISIBLE && parent != null;
     }
 
-    /**
-     * Tests whether the hierarchy, up to the scene root, changes visibility between
-     * start and end scenes. This is done to ensure that a view that changes visibility
-     * is only animated if that view's parent was stable between scenes; we should not
-     * fade an entire hierarchy, but rather just the top-most node in the hierarchy that
-     * changed visibility. Note that both the start and end parents are passed in
-     * because the instances may differ for the same view due to layout inflation
-     * between scenes.
-     *
-     * @param sceneRoot The root of the scene hierarchy
-     * @param startView The container view in the start scene
-     * @param endView The container view in the end scene
-     * @return true if the parent hierarchy experienced a visibility change, false
-     * otherwise
-     */
-    private boolean isHierarchyVisibilityChanging(ViewGroup sceneRoot, ViewGroup startView,
-            ViewGroup endView) {
-
-        if (startView == sceneRoot || endView == sceneRoot) {
-            return false;
-        }
-        TransitionValues startValues = startView != null ?
-                getTransitionValues(startView, true) : getTransitionValues(endView, true);
-        TransitionValues endValues = endView != null ?
-                getTransitionValues(endView, false) : getTransitionValues(startView, false);
-
-        if (startValues == null || endValues == null) {
-            return true;
-        }
-        Integer visibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
-        int startVisibility = (visibility != null) ? visibility : -1;
-        ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
-        visibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
-        int endVisibility = (visibility != null) ? visibility : -1;
-        ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
-        if (startVisibility != endVisibility || startParent != endParent) {
-            return true;
-        }
-
-        if (startParent != null || endParent != null) {
-            return isHierarchyVisibilityChanging(sceneRoot, startParent, endParent);
-        }
-        return false;
-    }
-
     private VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues,
             TransitionValues endValues) {
         final VisibilityInfo visInfo = new VisibilityInfo();
@@ -225,9 +164,7 @@
                 int endId = endView != null ? endView.getId() : -1;
                 isTarget = isValidTarget(startView, startId) || isValidTarget(endView, endId);
             }
-            if (isTarget || ((visInfo.startParent != null || visInfo.endParent != null) &&
-                    !isHierarchyVisibilityChanging(sceneRoot,
-                            visInfo.startParent, visInfo.endParent))) {
+            if (isTarget || ((visInfo.startParent != null || visInfo.endParent != null))) {
                 if (visInfo.fadeIn) {
                     return onAppear(sceneRoot, startValues, visInfo.startVisibility,
                             endValues, visInfo.endVisibility);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index aea2799c..caf9c8b 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -249,12 +249,6 @@
     boolean isSafeModeEnabled();
 
     /**
-     * Tell keyguard to show the assistant (Intent.ACTION_ASSIST) after asking for the user's
-     * credentials.
-     */
-    void showAssistant();
-
-    /**
      * Sets the display magnification callbacks. These callbacks notify
      * the client for contextual changes related to display magnification.
      *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 38f28ae..28f7480 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4825,8 +4825,8 @@
 
             enqueueInputEvent(new KeyEvent(event.getDownTime(), event.getEventTime(),
                         event.getAction(), keyCode, event.getRepeatCount(), event.getMetaState(),
-                        event.getScanCode(), event.getFlags() | KeyEvent.FLAG_FALLBACK,
-                        event.getSource()));
+                        event.getDeviceId(), event.getScanCode(),
+                        event.getFlags() | KeyEvent.FLAG_FALLBACK, event.getSource()));
             return true;
         }
 
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index e116662..79c0b3c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1176,12 +1176,6 @@
     public void dump(String prefix, PrintWriter writer, String[] args);
 
     /**
-     * Ask keyguard to invoke the assist intent after dismissing keyguard
-     * {@link android.content.Intent#ACTION_ASSIST}
-     */
-    public void showAssistant();
-
-    /**
      * Returns whether a given window type can be magnified.
      *
      * @param windowType The window type.
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index e7d84c2..59330ca 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -337,14 +337,17 @@
     public boolean deleteSurroundingText(int beforeLength, int afterLength);
 
     /**
-     * Set composing text around the current cursor position with the
-     * given text, and set the new cursor position. Any composing text
-     * set previously will be removed automatically.
+     * Replace the currently composing text with the given text, and
+     * set the new cursor position. Any composing text set previously
+     * will be removed automatically.
      *
      * <p>If there is any composing span currently active, all
      * characters that it comprises are removed. The passed text is
      * added in its place, and a composing span is added to this
-     * text. Finally, the cursor is moved to the location specified by
+     * text. If there is no composing span active, the passed text is
+     * added at the cursor position (removing selected characters
+     * first if any), and a composing span is added on the new text.
+     * Finally, the cursor is moved to the location specified by
      * <code>newCursorPosition</code>.</p>
      *
      * <p>This is usually called by IMEs to add or remove or change
@@ -447,8 +450,10 @@
      *
      * <p>This method removes the contents of the currently composing
      * text and replaces it with the passed CharSequence, and then
-     * moves the cursor according to {@code newCursorPosition}.
-     * This behaves like calling
+     * moves the cursor according to {@code newCursorPosition}. If there
+     * is no composing text when this method is called, the new text is
+     * inserted at the cursor position, removing text inside the selection
+     * if any. This behaves like calling
      * {@link #setComposingText(CharSequence, int) setComposingText(text, newCursorPosition)}
      * then {@link #finishComposingText()}.</p>
      *
@@ -461,15 +466,16 @@
      * but be careful to wait until the batch edit is over if one is
      * in progress.</p>
      *
-     * @param text The committed text. This may include styles.
-     * @param newCursorPosition The new cursor position around the text. If
-     *        > 0, this is relative to the end of the text - 1; if <= 0, this
-     *        is relative to the start of the text. So a value of 1 will
-     *        always advance you to the position after the full text being
-     *        inserted. Note that this means you can't position the cursor
-     *        within the text, because the editor can make modifications to
-     *        the text you are providing so it is not possible to correctly
-     *        specify locations there.
+     * @param text The text to commit. This may include styles.
+     * @param newCursorPosition The new cursor position around the text,
+     *        in Java characters. If > 0, this is relative to the end
+     *        of the text - 1; if <= 0, this is relative to the start
+     *        of the text. So a value of 1 will always advance the cursor
+     *        to the position after the full text being inserted. Note that
+     *        this means you can't position the cursor within the text,
+     *        because the editor can make modifications to the text
+     *        you are providing so it is not possible to correctly specify
+     *        locations there.
      * @return true on success, false if the input connection is no longer
      * valid.
      */
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index 6c4c39d..d4c5be0 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -26,9 +26,13 @@
 import android.view.ViewGroup;
 
 /**
- * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a 
- * {@link android.widget.ListView ListView} widget. The Cursor must include 
- * a column named "_id" or this class will not work.
+ * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a
+ * {@link android.widget.ListView ListView} widget.
+ * <p>
+ * The Cursor must include a column named "_id" or this class will not work.
+ * Additionally, using {@link android.database.MergeCursor} with this class will
+ * not work if the merged Cursors have overlapping values in their "_id"
+ * columns.
  */
 public abstract class CursorAdapter extends BaseAdapter implements Filterable,
         CursorFilter.CursorFilterClient {
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index b7e1fdd..66fe46f 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -28,6 +28,7 @@
 import android.util.AttributeSet;
 import android.util.IntProperty;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -76,6 +77,8 @@
     private int mDropDownVerticalOffset;
     private boolean mDropDownVerticalOffsetSet;
 
+    private int mDropDownGravity = Gravity.NO_GRAVITY;
+
     private boolean mDropDownAlwaysVisible = false;
     private boolean mForceIgnoreOutsideTouch = false;
     int mListItemExpandMaximum = Integer.MAX_VALUE;
@@ -439,6 +442,16 @@
     }
 
     /**
+     * Set the gravity of the dropdown list. This is commonly used to
+     * set gravity to START or END for alignment with the anchor.
+     *
+     * @param gravity Gravity value to use
+     */
+    public void setDropDownGravity(int gravity) {
+        mDropDownGravity = gravity;
+    }
+
+    /**
      * @return The width of the popup window in pixels.
      */
     public int getWidth() {
@@ -610,7 +623,7 @@
             mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
             mPopup.setTouchInterceptor(mTouchInterceptor);
             mPopup.showAsDropDown(getAnchorView(),
-                    mDropDownHorizontalOffset, mDropDownVerticalOffset);
+                    mDropDownHorizontalOffset, mDropDownVerticalOffset, mDropDownGravity);
             mDropDownList.setSelection(ListView.INVALID_POSITION);
             
             if (!mModal || mDropDownList.isInTouchMode()) {
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 9ac6a59..111dadc 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -22,6 +22,7 @@
 import com.android.internal.view.menu.SubMenuBuilder;
 
 import android.content.Context;
+import android.view.Gravity;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -64,12 +65,25 @@
      *               is room, or above it if there is not.
      */
     public PopupMenu(Context context, View anchor) {
+        this(context, anchor, Gravity.NO_GRAVITY);
+    }
+
+    /**
+     * Construct a new PopupMenu.
+     *
+     * @param context Context for the PopupMenu.
+     * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
+     *               is room, or above it if there is not.
+     * @param gravity The {@link Gravity} value for aligning the popup with its anchor
+     */
+    public PopupMenu(Context context, View anchor, int gravity) {
         // TODO Theme?
         mContext = context;
         mMenu = new MenuBuilder(context);
         mMenu.setCallback(this);
         mAnchor = anchor;
         mPopup = new MenuPopupHelper(context, mMenu, anchor);
+        mPopup.setGravity(gravity);
         mPopup.setCallback(this);
     }
 
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 1460737..5663959 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -72,7 +72,9 @@
      * screen as needed, regardless of whether this covers the input method.
      */
     public static final int INPUT_METHOD_NOT_NEEDED = 2;
-    
+
+    private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START;
+
     private Context mContext;
     private WindowManager mWindowManager;
     
@@ -135,12 +137,13 @@
                     WindowManager.LayoutParams p = (WindowManager.LayoutParams)
                             mPopupView.getLayoutParams();
 
-                    updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff));
+                    updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
+                            mAnchoredGravity));
                     update(p.x, p.y, -1, -1, true);
                 }
             }
         };
-    private int mAnchorXoff, mAnchorYoff;
+    private int mAnchorXoff, mAnchorYoff, mAnchoredGravity;
 
     private boolean mPopupViewInitialLayoutDirectionInherited;
 
@@ -873,15 +876,38 @@
      * location, the popup will be moved correspondingly.</p>
      *
      * @param anchor the view on which to pin the popup window
+     * @param xoff A horizontal offset from the anchor in pixels
+     * @param yoff A vertical offset from the anchor in pixels
      *
      * @see #dismiss()
      */
     public void showAsDropDown(View anchor, int xoff, int yoff) {
+        showAsDropDown(anchor, xoff, yoff, DEFAULT_ANCHORED_GRAVITY);
+    }
+
+    /**
+     * <p>Display the content view in a popup window anchored to the bottom-left
+     * corner of the anchor view offset by the specified x and y coordinates.
+     * If there is not enough room on screen to show
+     * the popup in its entirety, this method tries to find a parent scroll
+     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
+     * corner of the popup is pinned at the top left corner of the anchor view.</p>
+     * <p>If the view later scrolls to move <code>anchor</code> to a different
+     * location, the popup will be moved correspondingly.</p>
+     *
+     * @param anchor the view on which to pin the popup window
+     * @param xoff A horizontal offset from the anchor in pixels
+     * @param yoff A vertical offset from the anchor in pixels
+     * @param gravity Alignment of the popup relative to the anchor
+     *
+     * @see #dismiss()
+     */
+    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
         if (isShowing() || mContentView == null) {
             return;
         }
 
-        registerForScrollChanged(anchor, xoff, yoff);
+        registerForScrollChanged(anchor, xoff, yoff, gravity);
 
         mIsShowing = true;
         mIsDropdown = true;
@@ -889,7 +915,7 @@
         WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
         preparePopup(p);
 
-        updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff));
+        updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
 
         if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
         if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
@@ -1105,17 +1131,24 @@
      * @return true if the popup is translated upwards to fit on screen
      */
     private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p,
-            int xoff, int yoff) {
+            int xoff, int yoff, int gravity) {
 
         final int anchorHeight = anchor.getHeight();
         anchor.getLocationInWindow(mDrawingLocation);
         p.x = mDrawingLocation[0] + xoff;
         p.y = mDrawingLocation[1] + anchorHeight + yoff;
+
+        final int hgrav = Gravity.getAbsoluteGravity(gravity, anchor.getLayoutDirection()) &
+                Gravity.HORIZONTAL_GRAVITY_MASK;
+        if (hgrav == Gravity.RIGHT) {
+            // Flip the location to align the right sides of the popup and anchor instead of left
+            p.x -= mPopupWidth - anchor.getWidth();
+        }
         
         boolean onTop = false;
 
-        p.gravity = Gravity.START | Gravity.TOP;
-        
+        p.gravity = Gravity.LEFT | Gravity.TOP;
+
         anchor.getLocationOnScreen(mScreenLocation);
         final Rect displayFrame = new Rect();
         anchor.getWindowVisibleDisplayFrame(displayFrame);
@@ -1141,6 +1174,11 @@
             anchor.getLocationInWindow(mDrawingLocation);
             p.x = mDrawingLocation[0] + xoff;
             p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
+
+            // Preserve the gravity adjustment
+            if (hgrav == Gravity.RIGHT) {
+                p.x -= mPopupWidth - anchor.getWidth();
+            }
             
             // determine whether there is more space above or below the anchor
             anchor.getLocationOnScreen(mScreenLocation);
@@ -1148,7 +1186,7 @@
             onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) <
                     (mScreenLocation[1] - yoff - displayFrame.top);
             if (onTop) {
-                p.gravity = Gravity.START | Gravity.BOTTOM;
+                p.gravity = Gravity.LEFT | Gravity.BOTTOM;
                 p.y = root.getHeight() - mDrawingLocation[1] + yoff;
             } else {
                 p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
@@ -1436,7 +1474,7 @@
      * @param height the new height, can be -1 to ignore
      */
     public void update(View anchor, int width, int height) {
-        update(anchor, false, 0, 0, true, width, height);
+        update(anchor, false, 0, 0, true, width, height, mAnchoredGravity);
     }
 
     /**
@@ -1455,11 +1493,11 @@
      * @param height the new height, can be -1 to ignore
      */
     public void update(View anchor, int xoff, int yoff, int width, int height) {
-        update(anchor, true, xoff, yoff, true, width, height);
+        update(anchor, true, xoff, yoff, true, width, height, mAnchoredGravity);
     }
 
     private void update(View anchor, boolean updateLocation, int xoff, int yoff,
-            boolean updateDimension, int width, int height) {
+            boolean updateDimension, int width, int height, int gravity) {
 
         if (!isShowing() || mContentView == null) {
             return;
@@ -1468,11 +1506,12 @@
         WeakReference<View> oldAnchor = mAnchor;
         final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
-            registerForScrollChanged(anchor, xoff, yoff);
+            registerForScrollChanged(anchor, xoff, yoff, gravity);
         } else if (needsUpdate) {
             // No need to register again if this is a DropDown, showAsDropDown already did.
             mAnchorXoff = xoff;
             mAnchorYoff = yoff;
+            mAnchoredGravity = gravity;
         }
 
         WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
@@ -1494,9 +1533,10 @@
         int y = p.y;
 
         if (updateLocation) {
-            updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff));
+            updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
         } else {
-            updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff));            
+            updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
+                    mAnchoredGravity));
         }
 
         update(p.x, p.y, width, height, x != p.x || y != p.y);
@@ -1525,7 +1565,7 @@
         mAnchor = null;
     }
 
-    private void registerForScrollChanged(View anchor, int xoff, int yoff) {
+    private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
         unregisterForScrollChanged();
 
         mAnchor = new WeakReference<View>(anchor);
@@ -1536,6 +1576,7 @@
 
         mAnchorXoff = xoff;
         mAnchorYoff = yoff;
+        mAnchoredGravity = gravity;
     }
 
     private class PopupViewContainer extends FrameLayout {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ae1b627..61e071b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4679,8 +4679,6 @@
             assumeLayout();
         }
 
-        boolean changed = false;
-
         if (mMovement != null) {
             /* This code also provides auto-scrolling when a cursor is moved using a
              * CursorController (insertion point or selection limits).
@@ -4703,10 +4701,10 @@
             }
 
             if (curs >= 0) {
-                changed = bringPointIntoView(curs);
+                bringPointIntoView(curs);
             }
         } else {
-            changed = bringTextIntoView();
+            bringTextIntoView();
         }
 
         // This has to be checked here since:
@@ -4727,7 +4725,7 @@
         getViewTreeObserver().removeOnPreDrawListener(this);
         mPreDrawRegistered = false;
 
-        return !changed;
+        return true;
     }
 
     @Override
@@ -7838,7 +7836,15 @@
                                 getCompoundPaddingLeft() - getCompoundPaddingRight() -
                                 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
                     case Gravity.CENTER_HORIZONTAL:
-                        return 0.0f;
+                    case Gravity.FILL_HORIZONTAL:
+                        final int textDirection = mLayout.getParagraphDirection(0);
+                        if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
+                            return 0.0f;
+                        } else {
+                            return (mLayout.getLineRight(0) - (mRight - mLeft) -
+                                getCompoundPaddingLeft() - getCompoundPaddingRight() -
+                                mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
+                        }
                 }
             }
         }
@@ -7867,9 +7873,14 @@
                         return 0.0f;
                     case Gravity.CENTER_HORIZONTAL:
                     case Gravity.FILL_HORIZONTAL:
-                        return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
+                        final int textDirection = mLayout.getParagraphDirection(0);
+                        if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
+                            return 0.0f;
+                        } else {
+                            return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
                                 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
                                 getHorizontalFadingEdgeLength();
+                        }
                 }
             }
         }
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 009b729..fbdf318 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -33,6 +33,7 @@
 import android.media.SubtitleTrack.RenderingWidget;
 import android.media.WebVttRenderer;
 import android.net.Uri;
+import android.os.Looper;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pair;
@@ -879,4 +880,10 @@
 
         invalidate();
     }
+
+    /** @hide */
+    @Override
+    public Looper getSubtitleLooper() {
+        return Looper.getMainLooper();
+    }
 }
diff --git a/core/java/com/android/internal/transition/ActionBarTransition.java b/core/java/com/android/internal/transition/ActionBarTransition.java
index 8beae8c..c1065e7 100644
--- a/core/java/com/android/internal/transition/ActionBarTransition.java
+++ b/core/java/com/android/internal/transition/ActionBarTransition.java
@@ -19,7 +19,7 @@
 
 import android.transition.ChangeBounds;
 import android.transition.Fade;
-import android.transition.TextChange;
+import android.transition.ChangeText;
 import android.transition.Transition;
 import android.transition.TransitionManager;
 import android.transition.TransitionSet;
@@ -35,8 +35,8 @@
 
     static {
         if (TRANSITIONS_ENABLED) {
-            final TextChange tc = new TextChange();
-            tc.setChangeBehavior(TextChange.CHANGE_BEHAVIOR_OUT_IN);
+            final ChangeText tc = new ChangeText();
+            tc.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN);
             final TransitionSet inner = new TransitionSet();
             inner.addTransition(tc).addTransition(new ChangeBounds());
             final TransitionSet tg = new TransitionSet();
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
index 4c6ddbf..6471e14 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -25,6 +25,7 @@
 import android.transition.TransitionManager;
 import android.util.SparseBooleanArray;
 import android.view.ActionProvider;
+import android.view.Gravity;
 import android.view.MenuItem;
 import android.view.SoundEffectConstants;
 import android.view.View;
@@ -665,6 +666,7 @@
         public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
                 boolean overflowOnly) {
             super(context, menu, anchorView, overflowOnly);
+            setGravity(Gravity.END);
             setCallback(mPopupPresenterCallback);
         }
 
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index dbb78c2..05e9a66 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Parcelable;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
@@ -69,6 +70,8 @@
     /** Cached content width from {@link #measureContentWidth}. */
     private int mContentWidth;
 
+    private int mDropDownGravity = Gravity.NO_GRAVITY;
+
     public MenuPopupHelper(Context context, MenuBuilder menu) {
         this(context, menu, null, false);
     }
@@ -102,6 +105,10 @@
         mForceShowIcon = forceShow;
     }
 
+    public void setGravity(int gravity) {
+        mDropDownGravity = gravity;
+    }
+
     public void show() {
         if (!tryShow()) {
             throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
@@ -126,6 +133,7 @@
             if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
             anchor.addOnAttachStateChangeListener(this);
             mPopup.setAnchorView(anchor);
+            mPopup.setDropDownGravity(mDropDownGravity);
         } else {
             return false;
         }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index a6566d5..b5d74e8 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -29,12 +29,6 @@
 import android.os.Parcelable;
 import android.text.Layout;
 import android.text.TextUtils;
-import android.transition.ChangeBounds;
-import android.transition.Fade;
-import android.transition.TextChange;
-import android.transition.Transition;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
 import android.util.AttributeSet;
 import android.view.CollapsibleActionView;
 import android.view.Gravity;
diff --git a/docs/html/google/index.jd b/docs/html/google/index.jd
index 4020cf4..ce30bce 100644
--- a/docs/html/google/index.jd
+++ b/docs/html/google/index.jd
@@ -39,6 +39,7 @@
 
 
 
+
 <div style="margin-top:10px">
 <div class="col-6 normal-links" style="margin-left:0">
 
@@ -48,7 +49,7 @@
   </div>
     <h4><a href="{@docRoot}google/play-services/maps.html"
     >Google Maps</a></h4>
-    <p>The power of Google Maps is available to your app
+    <p>Include the power of Google Maps in your app
     with an embeddable map view. You can customize the map with
     markers and overlays, control the user's perspective, draw lines
     and shapes, and much more.</p>
@@ -68,6 +69,17 @@
 
 <div class="landing-cell">
   <div class="cell-icon">
+  <img src="{@docRoot}images/google/cloud-platform.png" width="40" >
+  </div>
+    <h4><a class="external-link" href="https://cloud.google.com/solutions/mobile"
+    >Google Cloud Platform</a></h4>
+    <p>Build and host the backend for your Android app at Google-scale. With an infrastructure
+    that is managed automatically, you can focus on your app. Then, scale to support
+    millions of users.</p>
+</div>
+
+<div class="landing-cell">
+  <div class="cell-icon">
   <img src="{@docRoot}images/google/gcm-cloud.png" width="40" >
   </div>
     <h4><a href="{@docRoot}google/gcm/index.html"
@@ -98,6 +110,18 @@
 
 <div class="landing-cell">
   <div class="cell-icon">
+    <img src="{@docRoot}images/google/wallet.png" width="40" />
+  </div>
+    <h4><a class="external-link" href="https://developers.google.com/commerce/wallet/instant-buy/"
+    >Google Wallet Instant Buy</a></h4>
+    <p>Provide fast and easy checkout in your app when selling physical goods and services.
+    Increase conversions by streamlining your purchase flow and reducing the amount of
+    information your customers need to enter.
+    </p>
+</div>
+
+<div class="landing-cell">
+  <div class="cell-icon">
 <img src="{@docRoot}images/google/analytics.png" width="40" />
   </div>
     <h4><a class="external-link" 
diff --git a/docs/html/guide/topics/ui/controls/checkbox.jd b/docs/html/guide/topics/ui/controls/checkbox.jd
index 99140b5..2a64e38 100644
--- a/docs/html/guide/topics/ui/controls/checkbox.jd
+++ b/docs/html/guide/topics/ui/controls/checkbox.jd
@@ -94,7 +94,7 @@
 android.view.View} that was clicked)</li>
 </ul>
 
-<p class="note"><strong>Tip:</strong> If you need to change the radio button state
+<p class="note"><strong>Tip:</strong> If you need to change the checkbox state
 yourself (such as when loading a saved {@link android.preference.CheckBoxPreference}),
 use the {@link android.widget.CompoundButton#setChecked(boolean)} or {@link
 android.widget.CompoundButton#toggle()} method.</p>
\ No newline at end of file
diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd
index 09767bf..d934c4b 100644
--- a/docs/html/guide/topics/ui/dialogs.jd
+++ b/docs/html/guide/topics/ui/dialogs.jd
@@ -593,7 +593,7 @@
                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        // Send the negative button event back to the host activity
-                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
                    }
                });
         return builder.create();
diff --git a/docs/html/images/google/cloud-platform.png b/docs/html/images/google/cloud-platform.png
new file mode 100644
index 0000000..90df8ed
--- /dev/null
+++ b/docs/html/images/google/cloud-platform.png
Binary files differ
diff --git a/docs/html/images/google/wallet.png b/docs/html/images/google/wallet.png
new file mode 100644
index 0000000..34cc11e
--- /dev/null
+++ b/docs/html/images/google/wallet.png
Binary files differ
diff --git a/docs/html/training/articles/security-ssl.jd b/docs/html/training/articles/security-ssl.jd
index d3f68e2..f52865a 100644
--- a/docs/html/training/articles/security-ssl.jd
+++ b/docs/html/training/articles/security-ssl.jd
@@ -250,7 +250,7 @@
 This is similar to an unknown certificate authority, so you can use the
 same approach from the previous section.</p>
 
-<p>You can create yout own {@link javax.net.ssl.TrustManager},
+<p>You can create your own {@link javax.net.ssl.TrustManager},
 this time trusting the server certificate directly. This has all of the
 downsides discussed earlier of tying your app directly to a certificate, but can be done
 securely. However, you should be careful to make sure your self-signed certificate has a
diff --git a/docs/html/training/articles/smp.jd b/docs/html/training/articles/smp.jd
index 0f667d7..7240eec 100644
--- a/docs/html/training/articles/smp.jd
+++ b/docs/html/training/articles/smp.jd
@@ -1057,7 +1057,7 @@
 fix them.  Before we do that, we need to discuss the use of a basic language
 feature.</p>
 
-<h4 id="volatile">C/C+++ and "volatile"</h4>
+<h4 id="volatile">C/C++ and "volatile"</h4>
 
 <p>When writing single-threaded code, declaring a variable “volatile” can be
 very useful.  The compiler will not omit or reorder accesses to volatile
diff --git a/docs/html/training/monitoring-device-state/connectivity-monitoring.jd b/docs/html/training/monitoring-device-state/connectivity-monitoring.jd
index 11a05e1..fb5096d 100644
--- a/docs/html/training/monitoring-device-state/connectivity-monitoring.jd
+++ b/docs/html/training/monitoring-device-state/connectivity-monitoring.jd
@@ -49,7 +49,8 @@
         (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
  
 NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
-boolean isConnected = activeNetwork.isConnectedOrConnecting();</pre>
+boolean isConnected = activeNetwork != null &&
+                      activeNetwork.isConnectedOrConnecting();</pre>
 
 
 <h2 id="DetermineType">Determine the Type of your Internet Connection</h2> 
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index a3c9dac..77ac235 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -576,29 +576,6 @@
           </li>
         </ul>
       </li>
-      <li class="nav-section">
-        <div class="nav-section-header">
-          <a href="<?cs var:toroot ?>training/id-auth/index.html"
-             description=
-             "How to remember the user by account, authenticate the user, acquire user permission
-             for the user's online data, and create custom accounts on the device."
-            >Remembering Users</a>
-        </div>
-        <ul>
-          <li><a href="<?cs var:toroot ?>training/id-auth/identify.html">
-            Remembering Your User
-          </a>
-          </li>
-          <li><a href="<?cs var:toroot ?>training/id-auth/authenticate.html">
-            Authenticating to OAuth2 Services
-          </a>
-          </li>
-          <li><a href="<?cs var:toroot ?>training/id-auth/custom_auth.html">
-            Creating a Custom Account Type
-          </a>
-          </li>
-        </ul>
-      </li>
 
       <li class="nav-section">
         <div class="nav-section-header">
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index deba2cc..def9aa7 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1772,6 +1772,9 @@
             mSelectedSubtitleTrackIndex = -1;
         }
         setOnSubtitleDataListener(null);
+        if (track == null) {
+            return;
+        }
         for (int i = 0; i < mInbandSubtitleTracks.length; i++) {
             if (mInbandSubtitleTracks[i] == track) {
                 Log.v(TAG, "Selecting subtitle track " + i);
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 5127479..06af5de 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -374,7 +374,7 @@
          * Called when a sound has completed loading.
          *
          * @param soundPool SoundPool object from the load() method
-         * @param soundPool the sample ID of the sound loaded.
+         * @param sampleId the sample ID of the sound loaded.
          * @param status the status of the load operation (0 = success)
          */
         public void onLoadComplete(SoundPool soundPool, int sampleId, int status);
diff --git a/media/java/android/media/SubtitleController.java b/media/java/android/media/SubtitleController.java
index 8090561..13205bc 100644
--- a/media/java/android/media/SubtitleController.java
+++ b/media/java/android/media/SubtitleController.java
@@ -21,6 +21,9 @@
 
 import android.content.Context;
 import android.media.SubtitleTrack.RenderingWidget;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.view.accessibility.CaptioningManager;
 
 /**
@@ -37,6 +40,34 @@
     private SubtitleTrack mSelectedTrack;
     private boolean mShowing;
     private CaptioningManager mCaptioningManager;
+    private Handler mHandler;
+
+    private static final int WHAT_SHOW = 1;
+    private static final int WHAT_HIDE = 2;
+    private static final int WHAT_SELECT_TRACK = 3;
+    private static final int WHAT_SELECT_DEFAULT_TRACK = 4;
+
+    private final Handler.Callback mCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+            case WHAT_SHOW:
+                doShow();
+                return true;
+            case WHAT_HIDE:
+                doHide();
+                return true;
+            case WHAT_SELECT_TRACK:
+                doSelectTrack((SubtitleTrack)msg.obj);
+                return true;
+            case WHAT_SELECT_DEFAULT_TRACK:
+                doSelectDefaultTrack();
+                return true;
+            default:
+                return false;
+            }
+        }
+    };
 
     private CaptioningManager.CaptioningChangeListener mCaptioningChangeListener =
         new CaptioningManager.CaptioningChangeListener() {
@@ -112,7 +143,7 @@
      * in-band data from the {@link MediaPlayer}.  However, this does
      * not change the subtitle visibility.
      *
-     * Must be called from the UI thread.
+     * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
      *
      * @param track The subtitle track to select.  This must be one of the
      *              tracks in {@link #getTracks}.
@@ -122,9 +153,15 @@
         if (track != null && !mTracks.contains(track)) {
             return false;
         }
+
+        processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_TRACK, track));
+        return true;
+    }
+
+    private void doSelectTrack(SubtitleTrack track) {
         mTrackIsExplicit = true;
         if (mSelectedTrack == track) {
-            return true;
+            return;
         }
 
         if (mSelectedTrack != null) {
@@ -145,7 +182,6 @@
         if (mListener != null) {
             mListener.onSubtitleTrackSelected(track);
         }
-        return true;
     }
 
     /**
@@ -170,8 +206,6 @@
      *
      * The default values for these flags are DEFAULT=no, AUTOSELECT=yes
      * and FORCED=no.
-     *
-     * Must be called from the UI thread.
      */
     public SubtitleTrack getDefaultTrack() {
         SubtitleTrack bestTrack = null;
@@ -226,8 +260,12 @@
     private boolean mTrackIsExplicit = false;
     private boolean mVisibilityIsExplicit = false;
 
-    /** @hide - called from UI thread */
+    /** @hide - should be called from anchor thread */
     public void selectDefaultTrack() {
+        processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_DEFAULT_TRACK));
+    }
+
+    private void doSelectDefaultTrack() {
         if (mTrackIsExplicit) {
             // If track selection is explicit, but visibility
             // is not, it falls back to the captioning setting
@@ -259,8 +297,9 @@
         }
     }
 
-    /** @hide - called from UI thread */
+    /** @hide - must be called from anchor thread */
     public void reset() {
+        checkAnchorLooper();
         hide();
         selectTrack(null);
         mTracks.clear();
@@ -301,9 +340,13 @@
     /**
      * Show the selected (or default) subtitle track.
      *
-     * Must be called from the UI thread.
+     * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
      */
     public void show() {
+        processOnAnchor(mHandler.obtainMessage(WHAT_SHOW));
+    }
+
+    private void doShow() {
         mShowing = true;
         mVisibilityIsExplicit = true;
         if (mSelectedTrack != null) {
@@ -314,9 +357,13 @@
     /**
      * Hide the selected (or default) subtitle track.
      *
-     * Must be called from the UI thread.
+     * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
      */
     public void hide() {
+        processOnAnchor(mHandler.obtainMessage(WHAT_HIDE));
+    }
+
+    private void doHide() {
         mVisibilityIsExplicit = true;
         if (mSelectedTrack != null) {
             mSelectedTrack.hide();
@@ -384,25 +431,53 @@
          * @hide
          */
         public void setSubtitleWidget(RenderingWidget subtitleWidget);
+
+        /**
+         * Anchors provide the looper on which all track visibility changes
+         * (track.show/hide, setSubtitleWidget) will take place.
+         * @hide
+         */
+        public Looper getSubtitleLooper();
     }
 
     private Anchor mAnchor;
 
-    /** @hide - called from UI thread */
+    /**
+     *  @hide - called from anchor's looper (if any, both when unsetting and
+     *  setting)
+     */
     public void setAnchor(Anchor anchor) {
         if (mAnchor == anchor) {
             return;
         }
 
         if (mAnchor != null) {
+            checkAnchorLooper();
             mAnchor.setSubtitleWidget(null);
         }
         mAnchor = anchor;
+        mHandler = null;
         if (mAnchor != null) {
+            mHandler = new Handler(mAnchor.getSubtitleLooper(), mCallback);
+            checkAnchorLooper();
             mAnchor.setSubtitleWidget(getRenderingWidget());
         }
     }
 
+    private void checkAnchorLooper() {
+        assert mHandler != null : "Should have a looper already";
+        assert Looper.myLooper() == mHandler.getLooper() : "Must be called from the anchor's looper";
+    }
+
+    private void processOnAnchor(Message m) {
+        assert mHandler != null : "Should have a looper already";
+        if (Looper.myLooper() == mHandler.getLooper()) {
+            mHandler.dispatchMessage(m);
+        } else {
+            mHandler.sendMessage(m);
+        }
+    }
+
     public interface Listener {
         /**
          * Called when a subtitle track has been selected.
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png
index 0240874..904d525 100644
--- a/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png
new file mode 100644
index 0000000..0d75172
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png
new file mode 100644
index 0000000..403eddb
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png
index 0240874..068619b 100644
--- a/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png
+++ b/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png
new file mode 100644
index 0000000..0d75172
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png
new file mode 100644
index 0000000..9a9cf5e
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png
index 0240874..e38a868 100644
--- a/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png
+++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png
new file mode 100644
index 0000000..0d75172
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png
new file mode 100644
index 0000000..205c34b
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png
index 0240874..0b332e4 100644
--- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png
+++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png
new file mode 100644
index 0000000..32b5f98
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png
new file mode 100644
index 0000000..f47d50a
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
index 3bea166..851061f 100644
--- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
@@ -21,17 +21,18 @@
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingTop="8dip"
-    android:paddingBottom="8dip"
-    android:orientation="horizontal">
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp"
+    android:gravity="center_vertical"
+    android:orientation="horizontal"
+    android:baselineAligned="false">
 
     <FrameLayout
         android:id="@android:id/icon"
         android:layout_width="@dimen/icon_size"
         android:layout_height="@dimen/icon_size"
         android:layout_marginStart="12dp"
-        android:layout_marginEnd="20dp"
-        android:layout_gravity="center_vertical">
+        android:layout_marginEnd="20dp">
 
         <ImageView
             android:id="@+id/icon_mime"
@@ -49,11 +50,11 @@
 
     </FrameLayout>
 
+    <!-- This is the one special case where we want baseline alignment! -->
     <LinearLayout
-        android:layout_width="0dip"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:layout_gravity="center_vertical"
         android:orientation="horizontal">
 
         <TextView
diff --git a/packages/DocumentsUI/res/layout-sw720dp/activity.xml b/packages/DocumentsUI/res/layout-sw720dp/activity.xml
index 78735fd..9286277 100644
--- a/packages/DocumentsUI/res/layout-sw720dp/activity.xml
+++ b/packages/DocumentsUI/res/layout-sw720dp/activity.xml
@@ -17,7 +17,8 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="horizontal">
+    android:orientation="horizontal"
+    android:baselineAligned="false">
 
     <FrameLayout
         android:layout_width="wrap_content"
@@ -47,7 +48,7 @@
         <com.android.documentsui.DirectoryContainerView
             android:id="@+id/container_directory"
             android:layout_width="match_parent"
-            android:layout_height="0dip"
+            android:layout_height="0dp"
             android:layout_weight="1" />
 
         <FrameLayout
diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml
index 9937c39..2ef7e9c 100644
--- a/packages/DocumentsUI/res/layout/activity.xml
+++ b/packages/DocumentsUI/res/layout/activity.xml
@@ -27,7 +27,7 @@
         <com.android.documentsui.DirectoryContainerView
             android:id="@+id/container_directory"
             android:layout_width="match_parent"
-            android:layout_height="0dip"
+            android:layout_height="0dp"
             android:layout_weight="1" />
 
         <FrameLayout
diff --git a/packages/DocumentsUI/res/layout/fragment_roots.xml b/packages/DocumentsUI/res/layout/fragment_roots.xml
index 09782d9..c3a3da0 100644
--- a/packages/DocumentsUI/res/layout/fragment_roots.xml
+++ b/packages/DocumentsUI/res/layout/fragment_roots.xml
@@ -18,4 +18,4 @@
     android:id="@android:id/list"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:divider="@null" />
+    android:divider="@drawable/ic_drawer_hairline_divider" />
diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml
index 570b517..891f0a0 100644
--- a/packages/DocumentsUI/res/layout/fragment_save.xml
+++ b/packages/DocumentsUI/res/layout/fragment_save.xml
@@ -29,6 +29,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
+        android:baselineAligned="false"
         android:gravity="center_vertical"
         android:background="#ddd"
         android:minHeight="?android:attr/listPreferredItemHeightSmall">
@@ -44,7 +45,7 @@
 
         <EditText
             android:id="@android:id/title"
-            android:layout_width="0dip"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:singleLine="true"
diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
index b745bb9..bb5dce1 100644
--- a/packages/DocumentsUI/res/layout/item_doc_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml
@@ -28,37 +28,25 @@
 
         <FrameLayout
             android:layout_width="match_parent"
-            android:layout_height="0dip"
+            android:layout_height="0dp"
             android:layout_weight="1"
             android:layout_marginBottom="6dp"
-            android:background="#fff">
-
-            <FrameLayout
-                android:id="@android:id/icon"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
-
-                <ImageView
-                    android:id="@+id/icon_mime"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:scaleType="centerInside"
-                    android:contentDescription="@null" />
-
-                <ImageView
-                    android:id="@+id/icon_thumb"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:scaleType="centerCrop"
-                    android:contentDescription="@null" />
-
-            </FrameLayout>
+            android:background="#fff"
+            android:foreground="@drawable/ic_grid_gradient_bg"
+            android:foregroundGravity="fill">
 
             <ImageView
+                android:id="@+id/icon_mime"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:scaleType="fitXY"
-                android:src="@drawable/ic_grid_gradient_bg"
+                android:scaleType="centerInside"
+                android:contentDescription="@null" />
+
+            <ImageView
+                android:id="@+id/icon_thumb"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scaleType="centerCrop"
                 android:contentDescription="@null" />
 
         </FrameLayout>
@@ -67,7 +55,9 @@
             android:id="@+id/line1"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:gravity="center_vertical"
             android:orientation="horizontal"
+            android:baselineAligned="false"
             android:paddingStart="?android:attr/listPreferredItemPaddingStart"
             android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
 
@@ -85,7 +75,7 @@
                 android:id="@android:id/icon1"
                 android:layout_width="@dimen/root_icon_size"
                 android:layout_height="@dimen/root_icon_size"
-                android:layout_marginStart="8dip"
+                android:layout_marginStart="8dp"
                 android:scaleType="centerInside"
                 android:contentDescription="@null" />
 
@@ -95,16 +85,17 @@
             android:id="@+id/line2"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:gravity="center_vertical"
             android:orientation="horizontal"
+            android:baselineAligned="false"
             android:paddingStart="?android:attr/listPreferredItemPaddingStart"
             android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
 
             <TextView
                 android:id="@+id/date"
-                android:layout_width="wrap_content"
+                android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:minWidth="80dp"
+                android:layout_weight="0.5"
                 android:singleLine="true"
                 android:ellipsize="marquee"
                 android:textAlignment="viewStart"
@@ -112,26 +103,20 @@
 
             <TextView
                 android:id="@+id/size"
-                android:layout_width="wrap_content"
+                android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
+                android:layout_weight="0.5"
                 android:layout_marginStart="8dp"
-                android:minWidth="80dp"
                 android:singleLine="true"
                 android:ellipsize="marquee"
                 android:textAlignment="viewStart"
                 style="@style/TextAppearance.Small" />
 
-            <Space
-                android:layout_width="0dp"
-                android:layout_height="0dp"
-                android:layout_weight="1" />
-
             <ImageView
                 android:id="@android:id/icon2"
                 android:layout_width="@dimen/root_icon_size"
                 android:layout_height="@dimen/root_icon_size"
-                android:layout_marginStart="8dip"
+                android:layout_marginStart="8dp"
                 android:scaleType="centerInside"
                 android:contentDescription="@null"
                 android:visibility="gone" />
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index 84fda9d..4c5b2e3 100644
--- a/packages/DocumentsUI/res/layout/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -21,17 +21,18 @@
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingTop="8dip"
-    android:paddingBottom="8dip"
-    android:orientation="horizontal">
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp"
+    android:gravity="center_vertical"
+    android:orientation="horizontal"
+    android:baselineAligned="false">
 
     <FrameLayout
         android:id="@android:id/icon"
         android:layout_width="@dimen/icon_size"
         android:layout_height="@dimen/icon_size"
         android:layout_marginStart="12dp"
-        android:layout_marginEnd="20dp"
-        android:layout_gravity="center_vertical">
+        android:layout_marginEnd="20dp">
 
         <ImageView
             android:id="@+id/icon_mime"
@@ -50,20 +51,20 @@
     </FrameLayout>
 
     <LinearLayout
-        android:layout_width="0dip"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:layout_gravity="center_vertical"
         android:orientation="vertical">
 
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="horizontal">
+            android:orientation="horizontal"
+            android:baselineAligned="false">
 
             <TextView
                 android:id="@android:id/title"
-                android:layout_width="0dip"
+                android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:singleLine="true"
@@ -75,7 +76,7 @@
                 android:id="@android:id/icon1"
                 android:layout_width="@dimen/root_icon_size"
                 android:layout_height="@dimen/root_icon_size"
-                android:layout_marginStart="8dip"
+                android:layout_marginStart="8dp"
                 android:scaleType="centerInside"
                 android:contentDescription="@null" />
 
@@ -85,13 +86,15 @@
             android:id="@+id/line2"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="horizontal">
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:baselineAligned="false">
 
             <TextView
                 android:id="@+id/date"
-                android:layout_width="wrap_content"
+                android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
+                android:layout_weight="0.25"
                 android:minWidth="70dp"
                 android:singleLine="true"
                 android:ellipsize="marquee"
@@ -100,11 +103,11 @@
 
             <TextView
                 android:id="@+id/size"
-                android:layout_width="wrap_content"
+                android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:minWidth="70dp"
+                android:layout_weight="0.25"
                 android:layout_marginStart="8dp"
+                android:minWidth="70dp"
                 android:singleLine="true"
                 android:ellipsize="marquee"
                 android:textAlignment="viewStart"
@@ -114,8 +117,7 @@
                 android:id="@android:id/summary"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:layout_gravity="center_vertical"
+                android:layout_weight="0.5"
                 android:layout_marginStart="8dp"
                 android:singleLine="true"
                 android:ellipsize="marquee"
diff --git a/packages/DocumentsUI/res/layout/item_loading_grid.xml b/packages/DocumentsUI/res/layout/item_loading_grid.xml
index 21be137..0bf6137 100644
--- a/packages/DocumentsUI/res/layout/item_loading_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_loading_grid.xml
@@ -20,8 +20,8 @@
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingTop="8dip"
-    android:paddingBottom="8dip"
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp"
     android:orientation="horizontal">
 
     <ProgressBar
diff --git a/packages/DocumentsUI/res/layout/item_loading_list.xml b/packages/DocumentsUI/res/layout/item_loading_list.xml
index 7da71e3..cdcd01d 100644
--- a/packages/DocumentsUI/res/layout/item_loading_list.xml
+++ b/packages/DocumentsUI/res/layout/item_loading_list.xml
@@ -20,9 +20,8 @@
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingTop="8dip"
-    android:paddingBottom="8dip"
-    android:orientation="horizontal">
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp">
 
     <ProgressBar
         android:layout_width="wrap_content"
diff --git a/packages/DocumentsUI/res/layout/item_message_list.xml b/packages/DocumentsUI/res/layout/item_message_list.xml
index ffda98c..2bcbc2d 100644
--- a/packages/DocumentsUI/res/layout/item_message_list.xml
+++ b/packages/DocumentsUI/res/layout/item_message_list.xml
@@ -21,15 +21,16 @@
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingTop="8dip"
-    android:paddingBottom="8dip"
-    android:orientation="horizontal">
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp"
+    android:orientation="horizontal"
+    android:baselineAligned="false">
 
     <ImageView
         android:id="@android:id/icon"
         android:layout_width="@android:dimen/app_icon_size"
         android:layout_height="@android:dimen/app_icon_size"
-        android:layout_marginEnd="8dip"
+        android:layout_marginEnd="8dp"
         android:layout_gravity="center_vertical"
         android:scaleType="centerInside"
         android:contentDescription="@null" />
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
index 98d78da..9b52d85 100644
--- a/packages/DocumentsUI/res/layout/item_root.xml
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -22,13 +22,14 @@
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
     android:gravity="center_vertical"
     android:orientation="horizontal"
+    android:baselineAligned="false"
     android:background="@drawable/item_root">
 
     <ImageView
         android:id="@android:id/icon"
         android:layout_width="@dimen/icon_size"
         android:layout_height="@dimen/icon_size"
-        android:layout_marginEnd="8dip"
+        android:layout_marginEnd="8dp"
         android:scaleType="centerInside"
         android:contentDescription="@null" />
 
diff --git a/packages/DocumentsUI/res/layout/item_root_spacer.xml b/packages/DocumentsUI/res/layout/item_root_spacer.xml
new file mode 100644
index 0000000..7d96ac8
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_root_spacer.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/ic_drawer_tall_divider" />
diff --git a/packages/DocumentsUI/res/layout/item_title.xml b/packages/DocumentsUI/res/layout/item_title.xml
index 7eb100a..58016f1 100644
--- a/packages/DocumentsUI/res/layout/item_title.xml
+++ b/packages/DocumentsUI/res/layout/item_title.xml
@@ -21,7 +21,8 @@
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
     android:gravity="center_vertical"
-    android:orientation="horizontal">
+    android:orientation="horizontal"
+    android:baselineAligned="false">
 
     <ImageView
         android:id="@+id/subdir"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index 9d92cd8..48bfaf0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -25,6 +25,7 @@
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
@@ -35,6 +36,8 @@
 
 import com.android.documentsui.model.DocumentInfo;
 
+import java.io.FileNotFoundException;
+
 /**
  * Dialog to create a new directory.
  */
@@ -64,24 +67,45 @@
             @Override
             public void onClick(DialogInterface dialog, int which) {
                 final String displayName = text1.getText().toString();
-
-                final DocumentsActivity activity = (DocumentsActivity) getActivity();
-                final DocumentInfo cwd = activity.getCurrentDirectory();
-
-                try {
-                    final Uri childUri = DocumentsContract.createDocument(
-                            resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, displayName);
-
-                    // Navigate into newly created child
-                    final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
-                    activity.onDocumentPicked(childDoc);
-                } catch (Exception e) {
-                    Toast.makeText(context, R.string.create_error, Toast.LENGTH_SHORT).show();
-                }
+                new CreateDirectoryTask(displayName).execute();
             }
         });
         builder.setNegativeButton(android.R.string.cancel, null);
 
         return builder.create();
     }
+
+    private class CreateDirectoryTask extends AsyncTask<Void, Void, DocumentInfo> {
+        private final String mDisplayName;
+
+        public CreateDirectoryTask(String displayName) {
+            mDisplayName = displayName;
+        }
+
+        @Override
+        protected DocumentInfo doInBackground(Void... params) {
+            final DocumentsActivity activity = (DocumentsActivity) getActivity();
+            final ContentResolver resolver = activity.getContentResolver();
+
+            final DocumentInfo cwd = activity.getCurrentDirectory();
+            final Uri childUri = DocumentsContract.createDocument(
+                    resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, mDisplayName);
+            try {
+                return DocumentInfo.fromUri(resolver, childUri);
+            } catch (FileNotFoundException e) {
+                return null;
+            }
+        }
+
+        @Override
+        protected void onPostExecute(DocumentInfo result) {
+            final DocumentsActivity activity = (DocumentsActivity) getActivity();
+            if (result != null) {
+                // Navigate into newly created child
+                activity.onDocumentPicked(result);
+            } else {
+                Toast.makeText(activity, R.string.create_error, Toast.LENGTH_SHORT).show();
+            }
+        }
+    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 138f523..1f11aed 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -742,7 +742,6 @@
             final View line1 = convertView.findViewById(R.id.line1);
             final View line2 = convertView.findViewById(R.id.line2);
 
-            final View icon = convertView.findViewById(android.R.id.icon);
             final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime);
             final ImageView iconThumb = (ImageView) convertView.findViewById(R.id.icon_thumb);
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
@@ -786,10 +785,12 @@
             // loaded in background.
             if (cacheHit) {
                 iconMime.setAlpha(0f);
+                iconMime.setImageDrawable(null);
                 iconThumb.setAlpha(1f);
             } else {
                 iconMime.setAlpha(1f);
                 iconThumb.setAlpha(0f);
+                iconThumb.setImageDrawable(null);
                 if (docIcon != 0) {
                     iconMime.setImageDrawable(
                             IconUtils.loadPackageIcon(context, docAuthority, docIcon));
@@ -895,12 +896,14 @@
             final boolean enabled = isDocumentEnabled(docMimeType, docFlags);
             if (enabled) {
                 setEnabledRecursive(convertView, true);
-                icon.setAlpha(1f);
+                iconMime.setAlpha(1f);
+                iconThumb.setAlpha(1f);
                 if (icon1 != null) icon1.setAlpha(1f);
                 if (icon2 != null) icon2.setAlpha(1f);
             } else {
                 setEnabledRecursive(convertView, false);
-                icon.setAlpha(0.5f);
+                iconMime.setAlpha(0.5f);
+                iconThumb.setAlpha(0.5f);
                 if (icon1 != null) icon1.setAlpha(0.5f);
                 if (icon2 != null) icon2.setAlpha(0.5f);
             }
@@ -991,10 +994,11 @@
                 mIconThumb.setTag(null);
                 mIconThumb.setImageBitmap(result);
 
-                mIconMime.setAlpha(1f);
+                final float targetAlpha = mIconMime.isEnabled() ? 1f : 0.5f;
+                mIconMime.setAlpha(targetAlpha);
                 mIconMime.animate().alpha(0f).start();
                 mIconThumb.setAlpha(0f);
-                mIconThumb.animate().alpha(1f).start();
+                mIconThumb.animate().alpha(targetAlpha).start();
             }
         }
     }
@@ -1060,16 +1064,16 @@
     private boolean isDocumentEnabled(String docMimeType, int docFlags) {
         final State state = getDisplayState(DirectoryFragment.this);
 
-        // Read-only files are disabled when creating
-        if (state.action == ACTION_CREATE && (docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
-            return false;
-        }
-
         // Directories are always enabled
         if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
             return true;
         }
 
+        // Read-only files are disabled when creating
+        if (state.action == ACTION_CREATE && (docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
+            return false;
+        }
+
         return MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 8627ecf..0b3ecf8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -63,6 +63,9 @@
 }
 
 public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
+
+    private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
+
     private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
 
     private final int mType;
@@ -164,8 +167,7 @@
 
             if (mType == DirectoryFragment.TYPE_SEARCH) {
                 // Filter directories out of search results, for now
-                cursor = new FilteringCursorWrapper(cursor, null, new String[] {
-                        Document.MIME_TYPE_DIR });
+                cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
             } else {
                 // Normal directories should have sorting applied
                 cursor = new SortingCursorWrapper(cursor, result.sortOrder);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 72fdc57..4caec8f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -854,14 +854,7 @@
         mState.stackTouched = true;
 
         if (!mRoots.isRecentsRoot(root)) {
-            try {
-                final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId);
-                final DocumentInfo doc = DocumentInfo.fromUri(getContentResolver(), uri);
-                mState.stack.push(doc);
-                mState.stackTouched = true;
-                onCurrentDirectoryChanged(ANIM_SIDE);
-            } catch (FileNotFoundException e) {
-            }
+            new PickRootTask(root).execute();
         } else {
             onCurrentDirectoryChanged(ANIM_SIDE);
         }
@@ -871,6 +864,34 @@
         }
     }
 
+    private class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
+        private RootInfo mRoot;
+
+        public PickRootTask(RootInfo root) {
+            mRoot = root;
+        }
+
+        @Override
+        protected DocumentInfo doInBackground(Void... params) {
+            try {
+                final Uri uri = DocumentsContract.buildDocumentUri(
+                        mRoot.authority, mRoot.documentId);
+                return DocumentInfo.fromUri(getContentResolver(), uri);
+            } catch (FileNotFoundException e) {
+                return null;
+            }
+        }
+
+        @Override
+        protected void onPostExecute(DocumentInfo result) {
+            if (result != null) {
+                mState.stack.push(result);
+                mState.stackTouched = true;
+                onCurrentDirectoryChanged(ANIM_SIDE);
+            }
+        }
+    }
+
     public void onAppPicked(ResolveInfo info) {
         final Intent intent = new Intent(getIntent());
         intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
@@ -909,7 +930,7 @@
             onCurrentDirectoryChanged(ANIM_DOWN);
         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
             // Explicit file picked, return
-            onFinished(doc.derivedUri);
+            new ExistingFinishTask(doc.derivedUri).execute();
         } else if (mState.action == ACTION_CREATE) {
             // Replace selected file
             SaveFragment.get(fm).setReplaceTarget(doc);
@@ -943,29 +964,19 @@
             for (int i = 0; i < size; i++) {
                 uris[i] = docs.get(i).derivedUri;
             }
-            onFinished(uris);
+            new ExistingFinishTask(uris).execute();
         }
     }
 
     public void onSaveRequested(DocumentInfo replaceTarget) {
-        onFinished(replaceTarget.derivedUri);
+        new ExistingFinishTask(replaceTarget.derivedUri).execute();
     }
 
     public void onSaveRequested(String mimeType, String displayName) {
-        final DocumentInfo cwd = getCurrentDirectory();
-
-        final Uri childUri = DocumentsContract.createDocument(
-                getContentResolver(), cwd.derivedUri, mimeType, displayName);
-        if (childUri != null) {
-            onFinished(childUri);
-        } else {
-            Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
-        }
+        new CreateFinishTask(mimeType, displayName).execute();
     }
 
-    private void onFinished(Uri... uris) {
-        Log.d(TAG, "onFinished() " + Arrays.toString(uris));
-
+    private void saveStackBlocking() {
         final ContentResolver resolver = getContentResolver();
         final ContentValues values = new ContentValues();
 
@@ -973,6 +984,7 @@
         if (mState.action == ACTION_CREATE) {
             // Remember stack for last create
             values.clear();
+            values.put(RecentColumns.KEY, mState.stack.buildKey());
             values.put(RecentColumns.STACK, rawStack);
             resolver.insert(RecentsProvider.buildRecent(), values);
         }
@@ -983,6 +995,10 @@
         values.put(ResumeColumns.STACK, rawStack);
         values.put(ResumeColumns.EXTERNAL, 0);
         resolver.insert(RecentsProvider.buildResume(packageName), values);
+    }
+
+    private void onFinished(Uri... uris) {
+        Log.d(TAG, "onFinished() " + Arrays.toString(uris));
 
         final Intent intent = new Intent();
         if (uris.length == 1) {
@@ -1008,6 +1024,56 @@
         finish();
     }
 
+    private class CreateFinishTask extends AsyncTask<Void, Void, Uri> {
+        private final String mMimeType;
+        private final String mDisplayName;
+
+        public CreateFinishTask(String mimeType, String displayName) {
+            mMimeType = mimeType;
+            mDisplayName = displayName;
+        }
+
+        @Override
+        protected Uri doInBackground(Void... params) {
+            final DocumentInfo cwd = getCurrentDirectory();
+            final Uri childUri = DocumentsContract.createDocument(
+                    getContentResolver(), cwd.derivedUri, mMimeType, mDisplayName);
+            if (childUri != null) {
+                saveStackBlocking();
+            }
+            return childUri;
+        }
+
+        @Override
+        protected void onPostExecute(Uri result) {
+            if (result != null) {
+                onFinished(result);
+            } else {
+                Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT)
+                        .show();
+            }
+        }
+    }
+
+    private class ExistingFinishTask extends AsyncTask<Void, Void, Void> {
+        private final Uri[] mUris;
+
+        public ExistingFinishTask(Uri... uris) {
+            mUris = uris;
+        }
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            saveStackBlocking();
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void result) {
+            onFinished(mUris);
+        }
+    }
+
     public static class State implements android.os.Parcelable {
         public int action;
         public String[] acceptMimes;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
index 5f56963..52d816f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
@@ -34,10 +34,15 @@
     private int mCount;
 
     public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) {
-        this(cursor, acceptMimes, null);
+        this(cursor, acceptMimes, null, Long.MIN_VALUE);
     }
 
     public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes, String[] rejectMimes) {
+        this(cursor, acceptMimes, rejectMimes, Long.MIN_VALUE);
+    }
+
+    public FilteringCursorWrapper(
+            Cursor cursor, String[] acceptMimes, String[] rejectMimes, long rejectBefore) {
         mCursor = cursor;
 
         final int count = cursor.getCount();
@@ -47,9 +52,14 @@
         while (cursor.moveToNext()) {
             final String mimeType = cursor.getString(
                     cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
+            final long lastModified = cursor.getLong(
+                    cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED));
             if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) {
                 continue;
             }
+            if (lastModified < rejectBefore) {
+                continue;
+            }
             if (MimePredicate.mimeMatches(acceptMimes, mimeType)) {
                 mPosition[mCount++] = cursor.getPosition();
             }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index e390456..9a4fb7d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -26,9 +26,11 @@
 import android.database.Cursor;
 import android.database.MergeCursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
+import android.text.format.DateUtils;
 import android.util.Log;
 
 import com.android.documentsui.DocumentsActivity.State;
@@ -54,17 +56,23 @@
 public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
     private static final boolean LOGD = true;
 
-    public static final int MAX_OUTSTANDING_RECENTS = 2;
+    // TODO: adjust for svelte devices
+    // TODO: add support for oneway queries to avoid wedging loader
+    private static final int MAX_OUTSTANDING_RECENTS = 2;
 
     /**
      * Time to wait for first pass to complete before returning partial results.
      */
-    public static final int MAX_FIRST_PASS_WAIT_MILLIS = 500;
+    private static final int MAX_FIRST_PASS_WAIT_MILLIS = 500;
 
-    /**
-     * Maximum documents from a single root.
-     */
-    public static final int MAX_DOCS_FROM_ROOT = 64;
+    /** Maximum documents from a single root. */
+    private static final int MAX_DOCS_FROM_ROOT = 64;
+
+    /** Ignore documents older than this age. */
+    private static final long REJECT_OLDER_THAN = 45 * DateUtils.DAY_IN_MILLIS;
+
+    /** MIME types that should always be excluded from recents. */
+    private static final String[] RECENT_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
 
     private static final ExecutorService sExecutor = buildExecutor();
 
@@ -173,6 +181,8 @@
             }
         }
 
+        final long rejectBefore = System.currentTimeMillis() - REJECT_OLDER_THAN;
+
         // Collect all finished tasks
         List<Cursor> cursors = Lists.newArrayList();
         for (RecentTask task : mTasks.values()) {
@@ -180,7 +190,7 @@
                 try {
                     final Cursor cursor = task.get();
                     final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
-                            cursor, mState.acceptMimes, new String[] { Document.MIME_TYPE_DIR }) {
+                            cursor, mState.acceptMimes, RECENT_REJECT_MIMES, rejectBefore) {
                         @Override
                         public void close() {
                             // Ignored, since we manage cursor lifecycle internally
@@ -203,11 +213,22 @@
         final DirectoryResult result = new DirectoryResult();
         result.sortOrder = SORT_ORDER_LAST_MODIFIED;
 
-        if (cursors.size() > 0) {
-            final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
-            final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder);
-            result.cursor = sorted;
+        // Hint to UI if we're still loading
+        final Bundle extras = new Bundle();
+        if (cursors.size() != mTasks.size()) {
+            extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
         }
+
+        final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
+        final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder) {
+            @Override
+            public Bundle getExtras() {
+                return extras;
+            }
+        };
+
+        result.cursor = sorted;
+
         return result;
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index c975382..3954173 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -66,6 +66,7 @@
  */
 public class RecentsCreateFragment extends Fragment {
 
+    private View mEmptyView;
     private ListView mListView;
 
     private DocumentStackAdapter mAdapter;
@@ -87,6 +88,8 @@
 
         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
 
+        mEmptyView = view.findViewById(android.R.id.empty);
+
         mListView = (ListView) view.findViewById(R.id.list);
         mListView.setOnItemClickListener(mItemListener);
 
@@ -189,6 +192,13 @@
 
         public void swapStacks(List<DocumentStack> stacks) {
             mStacks = stacks;
+
+            if (isEmpty()) {
+                mEmptyView.setVisibility(View.VISIBLE);
+            } else {
+                mEmptyView.setVisibility(View.GONE);
+            }
+
             notifyDataSetChanged();
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index 7386cae..4313fa7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -33,7 +33,7 @@
 public class RecentsProvider extends ContentProvider {
     private static final String TAG = "RecentsProvider";
 
-    public static final long MAX_HISTORY_IN_MILLIS = DateUtils.DAY_IN_MILLIS * 45;
+    public static final long MAX_HISTORY_IN_MILLIS = 45 * DateUtils.DAY_IN_MILLIS;
 
     private static final String AUTHORITY = "com.android.documentsui.recents";
 
@@ -56,6 +56,7 @@
     public static final String TABLE_RESUME = "resume";
 
     public static class RecentColumns {
+        public static final String KEY = "key";
         public static final String STACK = "stack";
         public static final String TIMESTAMP = "timestamp";
     }
@@ -99,16 +100,18 @@
         private static final int VERSION_INIT = 1;
         private static final int VERSION_AS_BLOB = 3;
         private static final int VERSION_ADD_EXTERNAL = 4;
+        private static final int VERSION_ADD_RECENT_KEY = 5;
 
         public DatabaseHelper(Context context) {
-            super(context, DB_NAME, null, VERSION_ADD_EXTERNAL);
+            super(context, DB_NAME, null, VERSION_ADD_RECENT_KEY);
         }
 
         @Override
         public void onCreate(SQLiteDatabase db) {
 
             db.execSQL("CREATE TABLE " + TABLE_RECENT + " (" +
-                    RecentColumns.STACK + " BLOB PRIMARY KEY ON CONFLICT REPLACE," +
+                    RecentColumns.KEY + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
+                    RecentColumns.STACK + " BLOB DEFAULT NULL," +
                     RecentColumns.TIMESTAMP + " INTEGER" +
                     ")");
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 15af8aa..e3908e9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -99,7 +99,8 @@
      */
     public void updateAsync() {
         // Special root for recents
-        mRecentsRoot.rootType = Root.ROOT_TYPE_SHORTCUT;
+        mRecentsRoot.authority = null;
+        mRecentsRoot.rootId = null;
         mRecentsRoot.icon = R.drawable.ic_root_recent;
         mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE;
         mRecentsRoot.title = mContext.getString(R.string.root_recent);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index d602622..2fb12bb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -26,7 +26,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
-import android.provider.DocumentsContract.Root;
 import android.text.TextUtils;
 import android.text.format.Formatter;
 import android.view.LayoutInflater;
@@ -37,16 +36,16 @@
 import android.widget.ArrayAdapter;
 import android.widget.ImageView;
 import android.widget.ListView;
-import android.widget.Space;
 import android.widget.TextView;
 
 import com.android.documentsui.DocumentsActivity.State;
-import com.android.documentsui.SectionedListAdapter.SectionAdapter;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.RootInfo;
 import com.android.internal.util.Objects;
+import com.google.common.collect.Lists;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
@@ -56,7 +55,7 @@
 public class RootsFragment extends Fragment {
 
     private ListView mList;
-    private SectionedRootsAdapter mAdapter;
+    private RootsAdapter mAdapter;
 
     private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
 
@@ -112,7 +111,7 @@
 
                 final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
 
-                mAdapter = new SectionedRootsAdapter(context, result, includeApps);
+                mAdapter = new RootsAdapter(context, result, includeApps);
                 mList.setAdapter(mAdapter);
 
                 onCurrentRootChanged();
@@ -154,136 +153,148 @@
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
-            final Object item = mAdapter.getItem(position);
-            if (item instanceof RootInfo) {
-                activity.onRootPicked((RootInfo) item, true);
-            } else if (item instanceof ResolveInfo) {
-                activity.onAppPicked((ResolveInfo) item);
+            final Item item = mAdapter.getItem(position);
+            if (item instanceof RootItem) {
+                activity.onRootPicked(((RootItem) item).root, true);
+            } else if (item instanceof AppItem) {
+                activity.onAppPicked(((AppItem) item).info);
             } else {
                 throw new IllegalStateException("Unknown root: " + item);
             }
         }
     };
 
-    private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter {
-        public RootsAdapter(Context context) {
-            super(context, 0);
+    private static abstract class Item {
+        private final int mLayoutId;
+
+        public Item(int layoutId) {
+            mLayoutId = layoutId;
+        }
+
+        public View getView(View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(parent.getContext())
+                        .inflate(mLayoutId, parent, false);
+            }
+            bindView(convertView);
+            return convertView;
+        }
+
+        public abstract void bindView(View convertView);
+    }
+
+    private static class RootItem extends Item {
+        public final RootInfo root;
+
+        public RootItem(RootInfo root) {
+            super(R.layout.item_root);
+            this.root = root;
         }
 
         @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final Context context = parent.getContext();
-            if (convertView == null) {
-                convertView = LayoutInflater.from(context)
-                        .inflate(R.layout.item_root, parent, false);
-            }
-
+        public void bindView(View convertView) {
             final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
             final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
 
-            final RootInfo root = getItem(position);
+            final Context context = convertView.getContext();
             icon.setImageDrawable(root.loadIcon(context));
             title.setText(root.title);
 
-            // Device summary is always available space
-            final String summaryText;
-            if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) {
+            // Show available space if no summary
+            String summaryText = root.summary;
+            if (TextUtils.isEmpty(summaryText) && root.availableBytes >= 0) {
                 summaryText = context.getString(R.string.root_available_bytes,
                         Formatter.formatFileSize(context, root.availableBytes));
-            } else {
-                summaryText = root.summary;
             }
 
             summary.setText(summaryText);
             summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
-
-            return convertView;
-        }
-
-        @Override
-        public View getHeaderView(View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = new Space(parent.getContext());
-            }
-            return convertView;
         }
     }
 
-    private static class AppsAdapter extends ArrayAdapter<ResolveInfo> implements SectionAdapter {
-        public AppsAdapter(Context context) {
-            super(context, 0);
+    private static class SpacerItem extends Item {
+        public SpacerItem() {
+            super(R.layout.item_root_spacer);
         }
 
         @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final Context context = parent.getContext();
-            final PackageManager pm = context.getPackageManager();
-            if (convertView == null) {
-                convertView = LayoutInflater.from(context)
-                        .inflate(R.layout.item_root, parent, false);
-            }
+        public void bindView(View convertView) {
+            // Nothing to bind
+        }
+    }
 
+    private static class AppItem extends Item {
+        public final ResolveInfo info;
+
+        public AppItem(ResolveInfo info) {
+            super(R.layout.item_root);
+            this.info = info;
+        }
+
+        @Override
+        public void bindView(View convertView) {
             final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
             final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
 
-            final ResolveInfo info = getItem(position);
+            final PackageManager pm = convertView.getContext().getPackageManager();
             icon.setImageDrawable(info.loadIcon(pm));
             title.setText(info.loadLabel(pm));
 
             // TODO: match existing summary behavior from disambig dialog
             summary.setVisibility(View.GONE);
-
-            return convertView;
-        }
-
-        @Override
-        public View getHeaderView(View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = LayoutInflater.from(parent.getContext())
-                        .inflate(R.layout.item_root_header, parent, false);
-            }
-
-            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
-            title.setText(R.string.root_type_apps);
-
-            return convertView;
         }
     }
 
-    private static class SectionedRootsAdapter extends SectionedListAdapter {
-        private final RootsAdapter mRecent;
-        private final RootsAdapter mServices;
-        private final RootsAdapter mShortcuts;
-        private final RootsAdapter mDevices;
-        private final AppsAdapter mApps;
+    private static class RootsAdapter extends ArrayAdapter<Item> {
+        public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) {
+            super(context, 0);
 
-        public SectionedRootsAdapter(
-                Context context, Collection<RootInfo> roots, Intent includeApps) {
-            mRecent = new RootsAdapter(context);
-            mServices = new RootsAdapter(context);
-            mShortcuts = new RootsAdapter(context);
-            mDevices = new RootsAdapter(context);
-            mApps = new AppsAdapter(context);
+            RootItem recents = null;
+            RootItem images = null;
+            RootItem videos = null;
+            RootItem audio = null;
+            RootItem downloads = null;
+
+            final List<RootInfo> clouds = Lists.newArrayList();
+            final List<RootInfo> locals = Lists.newArrayList();
 
             for (RootInfo root : roots) {
-                if (root.authority == null) {
-                    mRecent.add(root);
-                    continue;
+                if (root.isRecents()) {
+                    recents = new RootItem(root);
+                } else if (root.isExternalStorage()) {
+                    locals.add(root);
+                } else if (root.isDownloads()) {
+                    downloads = new RootItem(root);
+                } else if (root.isImages()) {
+                    images = new RootItem(root);
+                } else if (root.isVideos()) {
+                    videos = new RootItem(root);
+                } else if (root.isAudio()) {
+                    audio = new RootItem(root);
+                } else {
+                    clouds.add(root);
                 }
+            }
 
-                switch (root.rootType) {
-                    case Root.ROOT_TYPE_SERVICE:
-                        mServices.add(root);
-                        break;
-                    case Root.ROOT_TYPE_SHORTCUT:
-                        mShortcuts.add(root);
-                        break;
-                    case Root.ROOT_TYPE_DEVICE:
-                        mDevices.add(root);
-                        break;
-                }
+            final RootComparator comp = new RootComparator();
+            Collections.sort(clouds, comp);
+            Collections.sort(locals, comp);
+
+            if (recents != null) add(recents);
+
+            for (RootInfo cloud : clouds) {
+                add(new RootItem(cloud));
+            }
+
+            if (images != null) add(images);
+            if (videos != null) add(videos);
+            if (audio != null) add(audio);
+            if (downloads != null) add(downloads);
+
+            for (RootInfo local : locals) {
+                add(new RootItem(local));
             }
 
             if (includeApps != null) {
@@ -291,34 +302,53 @@
                 final List<ResolveInfo> infos = pm.queryIntentActivities(
                         includeApps, PackageManager.MATCH_DEFAULT_ONLY);
 
+                final List<AppItem> apps = Lists.newArrayList();
+
                 // Omit ourselves from the list
                 for (ResolveInfo info : infos) {
                     if (!context.getPackageName().equals(info.activityInfo.packageName)) {
-                        mApps.add(info);
+                        apps.add(new AppItem(info));
+                    }
+                }
+
+                if (apps.size() > 0) {
+                    add(new SpacerItem());
+                    for (Item item : apps) {
+                        add(item);
                     }
                 }
             }
+        }
 
-            final RootComparator comp = new RootComparator();
-            mServices.sort(comp);
-            mShortcuts.sort(comp);
-            mDevices.sort(comp);
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final Item item = getItem(position);
+            return item.getView(convertView, parent);
+        }
 
-            if (mRecent.getCount() > 0) {
-                addSection(mRecent);
+        @Override
+        public boolean areAllItemsEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return getItemViewType(position) != 1;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            final Item item = getItem(position);
+            if (item instanceof RootItem || item instanceof AppItem) {
+                return 0;
+            } else {
+                return 1;
             }
-            if (mServices.getCount() > 0) {
-                addSection(mServices);
-            }
-            if (mShortcuts.getCount() > 0) {
-                addSection(mShortcuts);
-            }
-            if (mDevices.getCount() > 0) {
-                addSection(mDevices);
-            }
-            if (mApps.getCount() > 0) {
-                addSection(mApps);
-            }
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 2;
         }
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
index 57fc7e4..1a47308 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
@@ -213,8 +213,13 @@
                 if (DocumentsContract.isDocumentUri(this, uri)) {
                     result += "; DOC_ID";
                 }
-                getContentResolver()
-                        .takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                try {
+                    getContentResolver().takePersistableUriPermission(
+                            uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                } catch (SecurityException e) {
+                    result += "; FAILED TO TAKE";
+                    Log.e(TAG, "Failed to take", e);
+                }
                 InputStream is = null;
                 try {
                     is = getContentResolver().openInputStream(uri);
@@ -222,7 +227,7 @@
                     result += "; read length=" + length;
                 } catch (Exception e) {
                     result += "; ERROR";
-                    Log.w(TAG, "Failed to read " + uri, e);
+                    Log.e(TAG, "Failed to read " + uri, e);
                 } finally {
                     IoUtils.closeQuietly(is);
                 }
@@ -235,15 +240,20 @@
                 if (DocumentsContract.isDocumentUri(this, uri)) {
                     result += "; DOC_ID";
                 }
-                getContentResolver()
-                        .takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                try {
+                    getContentResolver().takePersistableUriPermission(
+                            uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                } catch (SecurityException e) {
+                    result += "; FAILED TO TAKE";
+                    Log.e(TAG, "Failed to take", e);
+                }
                 OutputStream os = null;
                 try {
                     os = getContentResolver().openOutputStream(uri);
                     os.write("THE COMPLETE WORKS OF SHAKESPEARE".getBytes());
                 } catch (Exception e) {
                     result += "; ERROR";
-                    Log.w(TAG, "Failed to write " + uri, e);
+                    Log.e(TAG, "Failed to write " + uri, e);
                 } finally {
                     IoUtils.closeQuietly(os);
                 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
index 0a378c0..28bab6c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
@@ -71,6 +71,25 @@
         }
     }
 
+    /**
+     * Build key that uniquely identifies this stack. It omits most of the raw
+     * details included in {@link #write(DataOutputStream)}, since they change
+     * too regularly to be used as a key.
+     */
+    public String buildKey() {
+        final StringBuilder builder = new StringBuilder();
+        if (root != null) {
+            builder.append(root.authority).append('#');
+            builder.append(root.rootId).append('#');
+        } else {
+            builder.append("[null]").append('#');
+        }
+        for (DocumentInfo doc : this) {
+            builder.append(doc.documentId).append('#');
+        }
+        return builder.toString();
+    }
+
     @Override
     public void reset() {
         clear();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 014901a..e220c9e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -42,10 +42,10 @@
  */
 public class RootInfo implements Durable, Parcelable {
     private static final int VERSION_INIT = 1;
+    private static final int VERSION_DROP_TYPE = 2;
 
     public String authority;
     public String rootId;
-    public int rootType;
     public int flags;
     public int icon;
     public String title;
@@ -67,7 +67,6 @@
     public void reset() {
         authority = null;
         rootId = null;
-        rootType = 0;
         flags = 0;
         icon = 0;
         title = null;
@@ -85,10 +84,9 @@
     public void read(DataInputStream in) throws IOException {
         final int version = in.readInt();
         switch (version) {
-            case VERSION_INIT:
+            case VERSION_DROP_TYPE:
                 authority = DurableUtils.readNullableString(in);
                 rootId = DurableUtils.readNullableString(in);
-                rootType = in.readInt();
                 flags = in.readInt();
                 icon = in.readInt();
                 title = DurableUtils.readNullableString(in);
@@ -105,10 +103,9 @@
 
     @Override
     public void write(DataOutputStream out) throws IOException {
-        out.writeInt(VERSION_INIT);
+        out.writeInt(VERSION_DROP_TYPE);
         DurableUtils.writeNullableString(out, authority);
         DurableUtils.writeNullableString(out, rootId);
-        out.writeInt(rootType);
         out.writeInt(flags);
         out.writeInt(icon);
         DurableUtils.writeNullableString(out, title);
@@ -146,7 +143,6 @@
         final RootInfo root = new RootInfo();
         root.authority = authority;
         root.rootId = getCursorString(cursor, Root.COLUMN_ROOT_ID);
-        root.rootType = getCursorInt(cursor, Root.COLUMN_ROOT_TYPE);
         root.flags = getCursorInt(cursor, Root.COLUMN_FLAGS);
         root.icon = getCursorInt(cursor, Root.COLUMN_ICON);
         root.title = getCursorString(cursor, Root.COLUMN_TITLE);
@@ -162,25 +158,44 @@
         derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null;
 
         // TODO: remove these special case icons
-        if ("com.android.externalstorage.documents".equals(authority)) {
-            if ("documents".equals(rootId)) {
-                derivedIcon = R.drawable.ic_doc_text;
-            } else {
-                derivedIcon = R.drawable.ic_root_sdcard;
-            }
-        }
-        if ("com.android.providers.downloads.documents".equals(authority)) {
+        if (isExternalStorage()) {
+            derivedIcon = R.drawable.ic_root_sdcard;
+        } else if (isDownloads()) {
             derivedIcon = R.drawable.ic_root_download;
+        } else if (isImages()) {
+            derivedIcon = R.drawable.ic_doc_image;
+        } else if (isVideos()) {
+            derivedIcon = R.drawable.ic_doc_video;
+        } else if (isAudio()) {
+            derivedIcon = R.drawable.ic_doc_audio;
         }
-        if ("com.android.providers.media.documents".equals(authority)) {
-            if ("images_root".equals(rootId)) {
-                derivedIcon = R.drawable.ic_doc_image;
-            } else if ("videos_root".equals(rootId)) {
-                derivedIcon = R.drawable.ic_doc_video;
-            } else if ("audio_root".equals(rootId)) {
-                derivedIcon = R.drawable.ic_doc_audio;
-            }
-        }
+    }
+
+    public boolean isRecents() {
+        return authority == null && rootId == null;
+    }
+
+    public boolean isExternalStorage() {
+        return "com.android.externalstorage.documents".equals(authority);
+    }
+
+    public boolean isDownloads() {
+        return "com.android.providers.downloads.documents".equals(authority);
+    }
+
+    public boolean isImages() {
+        return "com.android.providers.media.documents".equals(authority)
+                && "images_root".equals(rootId);
+    }
+
+    public boolean isVideos() {
+        return "com.android.providers.media.documents".equals(authority)
+                && "videos_root".equals(rootId);
+    }
+
+    public boolean isAudio() {
+        return "com.android.providers.media.documents".equals(authority)
+                && "audio_root".equals(rootId);
     }
 
     @Override
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index ed28da5..9328b33 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -47,9 +47,8 @@
     // docId format: root:path/to/file
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
-            Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
-            Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
-            Root.COLUMN_AVAILABLE_BYTES,
+            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+            Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
     };
 
     private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
@@ -59,7 +58,6 @@
 
     private static class RootInfo {
         public String rootId;
-        public int rootType;
         public int flags;
         public String title;
         public String docId;
@@ -84,7 +82,6 @@
 
             final RootInfo root = new RootInfo();
             root.rootId = rootId;
-            root.rootType = Root.ROOT_TYPE_DEVICE;
             root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
                     | Root.FLAG_SUPPORTS_SEARCH;
             root.title = getContext().getString(R.string.root_internal_storage);
@@ -198,7 +195,6 @@
 
             final RowBuilder row = result.newRow();
             row.add(Root.COLUMN_ROOT_ID, root.rootId);
-            row.add(Root.COLUMN_ROOT_TYPE, root.rootType);
             row.add(Root.COLUMN_FLAGS, root.flags);
             row.add(Root.COLUMN_TITLE, root.title);
             row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
index e6fbb1b..5a15cd2 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
@@ -65,7 +65,7 @@
     private static final String MY_DOC_NULL = "myNull";
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
-            Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
+            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
             Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
             Root.COLUMN_AVAILABLE_BYTES,
     };
@@ -114,7 +114,6 @@
         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
         final RowBuilder row = result.newRow();
         row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID);
-        row.add(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE);
         row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS);
         row.add(Root.COLUMN_TITLE, "_Test title which is really long");
         row.add(Root.COLUMN_SUMMARY,
diff --git a/packages/Keyguard/res/layout-land/keyguard_host_view.xml b/packages/Keyguard/res/layout-land/keyguard_host_view.xml
index 87b8b59..eeb9ee7 100644
--- a/packages/Keyguard/res/layout-land/keyguard_host_view.xml
+++ b/packages/Keyguard/res/layout-land/keyguard_host_view.xml
@@ -51,6 +51,11 @@
             androidprv:layout_maxHeight="480dp" />
         <include layout="@layout/keyguard_multi_user_selector"/>
 
+        <View android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              androidprv:layout_childType="scrim"
+              android:background="#99000000" />
+
         <com.android.keyguard.KeyguardSecurityContainer
             android:id="@+id/keyguard_security_container"
             android:layout_width="wrap_content"
diff --git a/packages/Keyguard/res/layout-port/keyguard_host_view.xml b/packages/Keyguard/res/layout-port/keyguard_host_view.xml
index 355739e..8498dcf 100644
--- a/packages/Keyguard/res/layout-port/keyguard_host_view.xml
+++ b/packages/Keyguard/res/layout-port/keyguard_host_view.xml
@@ -55,6 +55,11 @@
                 android:layout_gravity="center"/>
         </FrameLayout>
 
+        <View android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              androidprv:layout_childType="scrim"
+              android:background="#99000000" />
+
         <com.android.keyguard.KeyguardSecurityContainer
             android:id="@+id/keyguard_security_container"
             android:layout_width="wrap_content"
diff --git a/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml b/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml
index 42dbe9d..77bc9b5 100644
--- a/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml
+++ b/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml
@@ -52,6 +52,11 @@
 
         <include layout="@layout/keyguard_multi_user_selector"/>
 
+        <View android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              androidprv:layout_childType="scrim"
+              android:background="#99000000" />
+
         <com.android.keyguard.KeyguardSecurityContainer
             android:id="@+id/keyguard_security_container"
             android:layout_width="wrap_content"
diff --git a/packages/Keyguard/res/layout/keyguard_transport_control_view.xml b/packages/Keyguard/res/layout/keyguard_transport_control_view.xml
index 7e36f9f..801999a 100644
--- a/packages/Keyguard/res/layout/keyguard_transport_control_view.xml
+++ b/packages/Keyguard/res/layout/keyguard_transport_control_view.xml
@@ -22,22 +22,16 @@
     android:gravity="center_horizontal"
     android:id="@+id/keyguard_transport_control">
 
-    <!-- FrameLayout used as scrim to show between album art and buttons -->
-    <FrameLayout
+    <!-- Use ImageView for its cropping features; otherwise could be android:background -->
+    <ImageView
+        android:id="@+id/albumart"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:foreground="@drawable/ic_lockscreen_player_background"
-        android:contentDescription="@string/keygaurd_accessibility_media_controls">
-        <!-- Use ImageView for its cropping features; otherwise could be android:background -->
-        <ImageView
-            android:id="@+id/albumart"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_gravity="fill"
-            android:scaleType="centerCrop"
-            android:adjustViewBounds="false"
-        />
-    </FrameLayout>
+        android:layout_gravity="fill"
+        android:scaleType="centerCrop"
+        android:adjustViewBounds="false"
+        android:contentDescription="@string/keygaurd_accessibility_media_controls" />
+
 
     <LinearLayout
         android:orientation="vertical"
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index a9e9d3a..f4c16c6 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
@@ -123,6 +123,8 @@
 
     protected boolean mShowSecurityWhenReturn;
 
+    private final Rect mInsets = new Rect();
+
     /*package*/ interface UserSwitcherCallback {
         void hideSecurityView(int duration);
         void showSecurityView();
@@ -405,11 +407,6 @@
         updateSecurityViews();
     }
 
-    public void setScrimView(View scrim) {
-        if (mSlidingChallengeLayout != null) mSlidingChallengeLayout.setScrimView(scrim);
-        if (mMultiPaneChallengeLayout != null) mMultiPaneChallengeLayout.setScrimView(scrim);
-    }
-
     private void setBackButtonEnabled(boolean enabled) {
         if (mContext instanceof Activity) return;  // always enabled in activity mode
         setSystemUiVisibility(enabled ?
@@ -1351,6 +1348,7 @@
     static class SavedState extends BaseSavedState {
         int transportState;
         int appWidgetToShow = AppWidgetManager.INVALID_APPWIDGET_ID;
+        Rect insets = new Rect();
 
         SavedState(Parcelable superState) {
             super(superState);
@@ -1360,6 +1358,7 @@
             super(in);
             this.transportState = in.readInt();
             this.appWidgetToShow = in.readInt();
+            this.insets = in.readParcelable(null);
         }
 
         @Override
@@ -1367,6 +1366,7 @@
             super.writeToParcel(out, flags);
             out.writeInt(this.transportState);
             out.writeInt(this.appWidgetToShow);
+            out.writeParcelable(insets, 0);
         }
 
         public static final Parcelable.Creator<SavedState> CREATOR
@@ -1391,6 +1391,7 @@
                 && mAppWidgetContainer.getWidgetPageIndex(mTransportControl) >= 0;
         ss.transportState =  showing ? TRANSPORT_VISIBLE : mTransportState;
         ss.appWidgetToShow = mAppWidgetToShow;
+        ss.insets.set(mInsets);
         return ss;
     }
 
@@ -1404,11 +1405,24 @@
         super.onRestoreInstanceState(ss.getSuperState());
         mTransportState = (ss.transportState);
         mAppWidgetToShow = ss.appWidgetToShow;
+        setInsets(ss.insets);
         if (DEBUG) Log.d(TAG, "onRestoreInstanceState, transport=" + mTransportState);
         post(mSwitchPageRunnable);
     }
 
     @Override
+    protected boolean fitSystemWindows(Rect insets) {
+        setInsets(insets);
+        return true;
+    }
+
+    private void setInsets(Rect insets) {
+        mInsets.set(insets);
+        if (mSlidingChallengeLayout != null) mSlidingChallengeLayout.setInsets(mInsets);
+        if (mMultiPaneChallengeLayout != null) mMultiPaneChallengeLayout.setInsets(mInsets);
+    }
+
+    @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
         if (DEBUG) Log.d(TAG, "Window is " + (hasWindowFocus ? "focused" : "unfocused"));
@@ -1469,6 +1483,7 @@
             if (DEBUGXPORT) Log.v(TAG, "remove transport");
             mAppWidgetContainer.removeWidget(getOrCreateTransportControl());
             mTransportControl = null;
+            KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(null);
         }
     }
 
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
index 3208aff..2a5f979 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
@@ -36,6 +36,7 @@
 import android.text.TextUtils;
 import android.text.style.ForegroundColorSpan;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
@@ -98,11 +99,9 @@
 
             case MSG_SET_ARTWORK:
                 if (mClientGeneration == msg.arg1) {
-                    if (mMetadata.bitmap != null) {
-                        mMetadata.bitmap.recycle();
-                    }
                     mMetadata.bitmap = (Bitmap) msg.obj;
-                    mAlbumArt.setImageBitmap(mMetadata.bitmap);
+                    KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(
+                            mMetadata.bitmap);
                 }
                 break;
 
@@ -223,7 +222,8 @@
     @Override
     protected void onSizeChanged (int w, int h, int oldw, int oldh) {
         if (mAttached) {
-            int dim = Math.min(512, Math.max(w, h));
+            final DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
+            int dim = Math.max(dm.widthPixels, dm.heightPixels);
             if (DEBUG) Log.v(TAG, "TCV uses bitmap size=" + dim);
             mAudioManager.remoteControlDisplayUsesBitmapSize(mIRCD, dim, dim);
         }
@@ -300,7 +300,8 @@
                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
         }
 
-        mAlbumArt.setImageBitmap(mMetadata.bitmap);
+        KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(
+                mMetadata.bitmap);
         final int flags = mTransportControlFlags;
         setVisibilityBasedOnFlag(mBtnPrev, flags, RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS);
         setVisibilityBasedOnFlag(mBtnNext, flags, RemoteControlClient.FLAG_KEY_MEDIA_NEXT);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f4bbf9a..734f517 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -249,11 +249,11 @@
             if (Intent.ACTION_TIME_TICK.equals(action)
                     || Intent.ACTION_TIME_CHANGED.equals(action)
                     || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_TIME_UPDATE));
+                mHandler.sendEmptyMessage(MSG_TIME_UPDATE);
             } else if (TelephonyIntents.SPN_STRINGS_UPDATED_ACTION.equals(action)) {
                 mTelephonyPlmn = getTelephonyPlmnFrom(intent);
                 mTelephonySpn = getTelephonySpnFrom(intent);
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_CARRIER_INFO_UPDATE));
+                mHandler.sendEmptyMessage(MSG_CARRIER_INFO_UPDATE);
             } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
                 final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
                 final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);
@@ -277,12 +277,12 @@
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state));
             } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
                     .equals(action)) {
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED));
+                mHandler.sendEmptyMessage(MSG_DPM_STATE_CHANGED);
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_REMOVED,
                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
             } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_BOOT_COMPLETED));
+                dispatchBootCompleted();
             }
         }
     };
@@ -407,6 +407,20 @@
         return sInstance;
     }
 
+    /**
+     * IMPORTANT: Must be called from UI thread.
+     */
+    public void dispatchSetBackground(Bitmap bmp) {
+        if (DEBUG) Log.d(TAG, "dispatchSetBackground");
+        final int count = mCallbacks.size();
+        for (int i = 0; i < count; i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onSetBackground(bmp);
+            }
+        }
+    }
+
     protected void handleSetGenerationId(int clientGeneration, boolean clearing, PendingIntent p) {
         mDisplayClientState.clientGeneration = clientGeneration;
         mDisplayClientState.clearing = clearing;
@@ -520,7 +534,7 @@
                 super.onChange(selfChange);
                 mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
                 if (mDeviceProvisioned) {
-                    mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED));
+                    mHandler.sendEmptyMessage(MSG_DEVICE_PROVISIONED);
                 }
                 if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
             }
@@ -536,7 +550,7 @@
         if (provisioned != mDeviceProvisioned) {
             mDeviceProvisioned = provisioned;
             if (mDeviceProvisioned) {
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED));
+                mHandler.sendEmptyMessage(MSG_DEVICE_PROVISIONED);
             }
         }
     }
@@ -582,6 +596,18 @@
     }
 
     /**
+     * This is exposed since {@link Intent#ACTION_BOOT_COMPLETED} is not sticky. If
+     * keyguard crashes sometime after boot, then it will never receive this
+     * broadcast and hence not handle the event. This method is ultimately called by
+     * PhoneWindowManager in this case.
+     */
+    protected void dispatchBootCompleted() {
+        if (!mBootCompleted) {
+            mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
+        }
+    }
+
+    /**
      * Handle {@link #MSG_BOOT_COMPLETED}
      */
     protected void handleBootCompleted() {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index b0511e5..e6dddab 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -17,6 +17,7 @@
 
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
+import android.graphics.Bitmap;
 import android.media.AudioManager;
 
 import com.android.internal.telephony.IccCardConstants;
@@ -135,4 +136,8 @@
      * Called when the emergency call button is pressed.
      */
     void onEmergencyCallAction() { }
+
+    public void onSetBackground(Bitmap bitmap) {
+        // THIS SPACE FOR RENT
+    }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
index a0e44d7..2084a16 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
@@ -16,6 +16,9 @@
 
 package com.android.keyguard;
 
+import android.app.PendingIntent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
 import com.android.internal.policy.IKeyguardShowCallback;
 import com.android.internal.widget.LockPatternUtils;
 
@@ -77,6 +80,14 @@
     private boolean mScreenOn = false;
     private LockPatternUtils mLockPatternUtils;
 
+    private KeyguardUpdateMonitorCallback mBackgroundChanger = new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onSetBackground(Bitmap bmp) {
+            mKeyguardHost.setCustomBackground(bmp != null ?
+                    new BitmapDrawable(mContext.getResources(), bmp) : null);
+        }
+    };
+
     public interface ShowListener {
         void onShown(IBinder windowToken);
     };
@@ -139,11 +150,26 @@
 
     class ViewManagerHost extends FrameLayout {
         private static final int BACKGROUND_COLOR = 0x70000000;
+
+        private Drawable mCustomBackground;
+
         // This is a faster way to draw the background on devices without hardware acceleration
         private final Drawable mBackgroundDrawable = new Drawable() {
             @Override
             public void draw(Canvas canvas) {
-                canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC);
+                if (mCustomBackground != null) {
+                    final Rect bounds = mCustomBackground.getBounds();
+                    final int vWidth = getWidth();
+                    final int vHeight = getHeight();
+
+                    final int restore = canvas.save();
+                    canvas.translate(-(bounds.width() - vWidth) / 2,
+                            -(bounds.height() - vHeight) / 2);
+                    mCustomBackground.draw(canvas);
+                    canvas.restoreToCount(restore);
+                } else {
+                    canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC);
+                }
             }
 
             @Override
@@ -159,54 +185,44 @@
                 return PixelFormat.TRANSLUCENT;
             }
         };
-        private final View mScrimView;
-        private boolean mExtendIntoPadding;
-        public ViewManagerHost(Context context, boolean extendIntoPadding) {
+
+        public ViewManagerHost(Context context) {
             super(context);
-            mExtendIntoPadding = extendIntoPadding;
-            setFitsSystemWindows(true);
-            setClipToPadding(!mExtendIntoPadding);
             setBackground(mBackgroundDrawable);
-
-            mScrimView = new View(context);
-            mScrimView.setVisibility(View.GONE);
-            mScrimView.setBackgroundColor(0x99000000);
-            addView(mScrimView);
         }
 
-        private boolean considerPadding(View child) {
-            return !mExtendIntoPadding || child instanceof KeyguardHostView;
+        public void setCustomBackground(Drawable d) {
+            mCustomBackground = d;
+            if (d != null) {
+                d.setColorFilter(BACKGROUND_COLOR, PorterDuff.Mode.SRC_OVER);
+            }
+            computeCustomBackgroundBounds();
+            invalidate();
         }
 
-        @Override
-        protected void measureChildWithMargins(View child,
-                int parentWidthMeasureSpec, int widthUsed,
-                int parentHeightMeasureSpec, int heightUsed) {
-            if (considerPadding(child)) {
-                // don't extend into padding (default behavior)
-                super.measureChildWithMargins(child,
-                        parentWidthMeasureSpec, widthUsed,
-                        parentHeightMeasureSpec, heightUsed);
+        private void computeCustomBackgroundBounds() {
+            if (mCustomBackground == null) return; // Nothing to do
+            if (!isLaidOut()) return; // We'll do this later
+
+            final int bgWidth = mCustomBackground.getIntrinsicWidth();
+            final int bgHeight = mCustomBackground.getIntrinsicHeight();
+            final int vWidth = getWidth();
+            final int vHeight = getHeight();
+
+            final float bgAspect = (float) bgWidth / bgHeight;
+            final float vAspect = (float) vWidth / vHeight;
+
+            if (bgAspect > vAspect) {
+                mCustomBackground.setBounds(0, 0, (int) (vHeight * bgAspect), vHeight);
             } else {
-                // allowed to extend into padding (scrim / camera preview)
-                child.measure(parentWidthMeasureSpec, parentHeightMeasureSpec);
+                mCustomBackground.setBounds(0, 0, vWidth, (int) (vWidth / bgAspect));
             }
         }
 
         @Override
-        protected void onLayout(boolean changed, int l, int t, int r, int b) {
-            final int count = getChildCount();
-            for (int i = 0; i < count; i++) {
-                final View child = getChildAt(i);
-                int cl = l, ct = t, cr = r, cb = b;
-                if (considerPadding(child)) {
-                    cl += mPaddingLeft;
-                    ct += mPaddingTop;
-                    cr -= mPaddingRight;
-                    cb -= mPaddingBottom;
-                }
-                child.layout(cl, ct, cr, cb);
-            }
+        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+            super.onSizeChanged(w, h, oldw, oldh);
+            computeCustomBackgroundBounds();
         }
 
         @Override
@@ -252,7 +268,7 @@
         if (mKeyguardHost == null) {
             if (DEBUG) Log.d(TAG, "keyguard host is null, creating it...");
 
-            mKeyguardHost = new ViewManagerHost(mContext, shouldEnableTransparentBars());
+            mKeyguardHost = new ViewManagerHost(mContext);
 
             int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                     | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
@@ -282,9 +298,13 @@
             lp.setTitle("Keyguard");
             mWindowLayoutParams = lp;
             mViewManager.addView(mKeyguardHost, lp);
+
+            KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mBackgroundChanger);
         }
 
         if (force || mKeyguardView == null) {
+            mKeyguardHost.setCustomBackground(null);
+            mKeyguardHost.removeAllViews();
             inflateKeyguardView(options);
             mKeyguardView.requestFocus();
         }
@@ -306,7 +326,6 @@
         mKeyguardView.setViewMediatorCallback(mViewMediatorCallback);
         mKeyguardView.initializeSwitchingUserState(options != null &&
                 options.getBoolean(IS_SWITCHING_USER));
-        mKeyguardView.setScrimView(mKeyguardHost.mScrimView);
 
         // HACK
         // The keyguard view will have set up window flags in onFinishInflate before we set
@@ -471,6 +490,8 @@
                     public void run() {
                         synchronized (KeyguardViewManager.this) {
                             lastView.cleanUp();
+                            // Let go of any large bitmaps.
+                            mKeyguardHost.setCustomBackground(null);
                             mKeyguardHost.removeView(lastView);
                         }
                     }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java
index 0606d83..ec3eb15 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import android.graphics.Bitmap;
 import com.android.internal.policy.IKeyguardExitCallback;
 import com.android.internal.policy.IKeyguardShowCallback;
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
@@ -528,6 +529,9 @@
             mSystemReady = true;
             mUpdateMonitor.registerCallback(mUpdateCallback);
 
+            // Send boot completed message if it hasn't already been sent.
+            mUpdateMonitor.dispatchBootCompleted();
+
             // Suppress biometric unlock right after boot until things have settled if it is the
             // selected security method, otherwise unsuppress it.  It must be unsuppressed if it is
             // not the selected security method for the following reason:  if the user starts
diff --git a/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java
index 76a7fe3..67d0d5a 100644
--- a/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java
+++ b/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java
@@ -28,6 +28,7 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
 import android.widget.LinearLayout;
 
 public class MultiPaneChallengeLayout extends ViewGroup implements ChallengeLayout {
@@ -47,6 +48,7 @@
 
     private final Rect mTempRect = new Rect();
     private final Rect mZeroPadding = new Rect();
+    private final Rect mInsets = new Rect();
 
     private final DisplayMetrics mDisplayMetrics;
 
@@ -80,6 +82,10 @@
         setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE);
     }
 
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+    }
+
     @Override
     public boolean isChallengeShowing() {
         return true;
@@ -187,7 +193,7 @@
             // This calculation is super dodgy and relies on several assumptions.
             // Specifically that the root of the window will be padded in for insets
             // and that the window is LAYOUT_IN_SCREEN.
-            virtualHeight = mDisplayMetrics.heightPixels - root.getPaddingTop();
+            virtualHeight = mDisplayMetrics.heightPixels - root.getPaddingTop() - mInsets.top;
         }
         if (lp.childType == LayoutParams.CHILD_TYPE_WIDGET ||
                 lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) {
@@ -213,6 +219,9 @@
         final int height = MeasureSpec.getSize(heightSpec);
         setMeasuredDimension(width, height);
 
+        final int insetHeight = height - mInsets.top - mInsets.bottom;
+        final int insetHeightSpec = MeasureSpec.makeMeasureSpec(insetHeight, MeasureSpec.EXACTLY);
+
         int widthUsed = 0;
         int heightUsed = 0;
 
@@ -245,14 +254,14 @@
                 if (child.getVisibility() == GONE) continue;
 
                 int adjustedWidthSpec = widthSpec;
-                int adjustedHeightSpec = heightSpec;
+                int adjustedHeightSpec = insetHeightSpec;
                 if (lp.maxWidth >= 0) {
                     adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
                             Math.min(lp.maxWidth, width), MeasureSpec.EXACTLY);
                 }
                 if (lp.maxHeight >= 0) {
                     adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
-                            Math.min(lp.maxHeight, height), MeasureSpec.EXACTLY);
+                            Math.min(lp.maxHeight, insetHeight), MeasureSpec.EXACTLY);
                 }
                 // measureChildWithMargins will resolve layout direction for the LayoutParams
                 measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0);
@@ -282,7 +291,7 @@
                 continue;
             }
 
-            final int virtualHeight = getVirtualHeight(lp, height, heightUsed);
+            final int virtualHeight = getVirtualHeight(lp, insetHeight, heightUsed);
 
             int adjustedWidthSpec;
             int adjustedHeightSpec;
@@ -330,11 +339,12 @@
         padding.bottom = getPaddingBottom();
         final int width = r - l;
         final int height = b - t;
+        final int insetHeight = height - mInsets.top - mInsets.bottom;
 
         // Reserve extra space in layout for the user switcher by modifying
         // local padding during this layout pass
         if (mUserSwitcherView != null && mUserSwitcherView.getVisibility() != GONE) {
-            layoutWithGravity(width, height, mUserSwitcherView, padding, true);
+            layoutWithGravity(width, insetHeight, mUserSwitcherView, padding, true);
         }
 
         final int count = getChildCount();
@@ -349,11 +359,11 @@
                 child.layout(0, 0, width, height);
                 continue;
             } else if (lp.childType == LayoutParams.CHILD_TYPE_PAGE_DELETE_DROP_TARGET) {
-                layoutWithGravity(width, height, child, mZeroPadding, false);
+                layoutWithGravity(width, insetHeight, child, mZeroPadding, false);
                 continue;
             }
 
-            layoutWithGravity(width, height, child, padding, false);
+            layoutWithGravity(width, insetHeight, child, padding, false);
         }
     }
 
@@ -445,6 +455,8 @@
                 right = left + childWidth;
                 break;
         }
+        top += mInsets.top;
+        bottom += mInsets.top;
         child.layout(left, top, right, bottom);
     }
 
diff --git a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
index 4a4e7fa..2e47768 100644
--- a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
+++ b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
@@ -24,6 +24,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.FloatProperty;
@@ -125,6 +126,7 @@
     private ObjectAnimator mFrameAnimation;
 
     private boolean mHasGlowpad;
+    private final Rect mInsets = new Rect();
 
     // We have an internal and external version, and we and them together.
     private boolean mChallengeInteractiveExternal = true;
@@ -261,6 +263,10 @@
         setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE);
     }
 
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+    }
+
     public void setHandleAlpha(float alpha) {
         if (mExpandChallengeView != null) {
             mExpandChallengeView.setAlpha(alpha);
@@ -797,11 +803,13 @@
             throw new IllegalArgumentException(
                     "SlidingChallengeLayout must be measured with an exact size");
         }
-
         final int width = MeasureSpec.getSize(widthSpec);
         final int height = MeasureSpec.getSize(heightSpec);
         setMeasuredDimension(width, height);
 
+        final int insetHeight = height - mInsets.top - mInsets.bottom;
+        final int insetHeightSpec = MeasureSpec.makeMeasureSpec(insetHeight, MeasureSpec.EXACTLY);
+
         // Find one and only one challenge view.
         final View oldChallengeView = mChallengeView;
         final View oldExpandChallengeView = mChallengeView;
@@ -861,13 +869,13 @@
             // We base this on the layout_maxHeight on the challenge view. If it comes out
             // negative or zero, either we didn't have a maxHeight or we're totally out of space,
             // so give up and measure as if this rule weren't there.
-            int challengeHeightSpec = heightSpec;
+            int challengeHeightSpec = insetHeightSpec;
             final View root = getRootView();
             if (root != null) {
                 final LayoutParams lp = (LayoutParams) mChallengeView.getLayoutParams();
-                final int specSize = MeasureSpec.getSize(heightSpec);
-                final int windowHeight = mDisplayMetrics.heightPixels - root.getPaddingTop();
-                final int diff = windowHeight - specSize;
+                final int windowHeight = mDisplayMetrics.heightPixels
+                        - root.getPaddingTop() - mInsets.top;
+                final int diff = windowHeight - insetHeight;
                 final int maxChallengeHeight = lp.maxHeight - diff;
                 if (maxChallengeHeight > 0) {
                     challengeHeightSpec = makeChildMeasureSpec(maxChallengeHeight, lp.height);
@@ -887,7 +895,7 @@
 
             // Measure children. Widget frame measures special, so that we can ignore
             // insets for the IME.
-            int parentWidthSpec = widthSpec, parentHeightSpec = heightSpec;
+            int parentWidthSpec = widthSpec, parentHeightSpec = insetHeightSpec;
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             if (lp.childType == LayoutParams.CHILD_TYPE_WIDGETS) {
                 final View root = getRootView();
@@ -896,12 +904,17 @@
                     // Specifically that the root of the window will be padded in for insets
                     // and that the window is LAYOUT_IN_SCREEN.
                     final int windowWidth = mDisplayMetrics.widthPixels;
-                    final int windowHeight = mDisplayMetrics.heightPixels - root.getPaddingTop();
+                    final int windowHeight = mDisplayMetrics.heightPixels
+                            - root.getPaddingTop() - mInsets.top;
                     parentWidthSpec = MeasureSpec.makeMeasureSpec(
                             windowWidth, MeasureSpec.EXACTLY);
                     parentHeightSpec = MeasureSpec.makeMeasureSpec(
                             windowHeight, MeasureSpec.EXACTLY);
                 }
+            } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
+                // Allow scrim views to extend into the insets
+                parentWidthSpec = widthSpec;
+                parentHeightSpec = heightSpec;
             }
             measureChildWithMargins(child, parentWidthSpec, 0, parentHeightSpec, 0);
         }
@@ -931,7 +944,7 @@
                 final int childWidth = child.getMeasuredWidth();
                 final int childHeight = child.getMeasuredHeight();
                 final int left = center - childWidth / 2;
-                final int layoutBottom = height - paddingBottom - lp.bottomMargin;
+                final int layoutBottom = height - paddingBottom - lp.bottomMargin - mInsets.bottom;
                 // We use the top of the challenge view to position the handle, so
                 // we never want less than the handle size showing at the bottom.
                 final int bottom = layoutBottom + (int) ((childHeight - mChallengeBottomBound)
@@ -942,15 +955,18 @@
                 final int center = (paddingLeft + width - paddingRight) / 2;
                 final int left = center - child.getMeasuredWidth() / 2;
                 final int right = left + child.getMeasuredWidth();
-                final int bottom = height - paddingBottom - lp.bottomMargin;
+                final int bottom = height - paddingBottom - lp.bottomMargin - mInsets.bottom;
                 final int top = bottom - child.getMeasuredHeight();
                 child.layout(left, top, right, bottom);
+            } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
+                // Scrim views use the entire area, including padding & insets
+                child.layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
             } else {
                 // Non-challenge views lay out from the upper left, layered.
                 child.layout(paddingLeft + lp.leftMargin,
-                        paddingTop + lp.topMargin,
+                        paddingTop + lp.topMargin + mInsets.top,
                         paddingLeft + child.getMeasuredWidth(),
-                        paddingTop + child.getMeasuredHeight());
+                        paddingTop + child.getMeasuredHeight() + mInsets.top);
             }
         }
 
@@ -1076,7 +1092,7 @@
 
         final int layoutBottom = getLayoutBottom();
         final int challengeHeight = mChallengeView.getMeasuredHeight();
-        return layoutBottom - challengeHeight;
+        return layoutBottom - challengeHeight - mInsets.top;
     }
 
     /**
@@ -1125,7 +1141,8 @@
         final int bottomMargin = (mChallengeView == null)
                 ? 0
                 : ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin;
-        final int layoutBottom = getMeasuredHeight() - getPaddingBottom() - bottomMargin;
+        final int layoutBottom = getMeasuredHeight() - getPaddingBottom() - bottomMargin
+                - mInsets.bottom;
         return layoutBottom;
     }
 
diff --git a/packages/PrintSpooler/Android.mk b/packages/PrintSpooler/Android.mk
index f65fe4b..9e7b969 100644
--- a/packages/PrintSpooler/Android.mk
+++ b/packages/PrintSpooler/Android.mk
@@ -24,7 +24,4 @@
 
 LOCAL_JAVA_LIBRARIES := framework-base
 
-LOCAL_PROGUARD_ENABLED := disabled
-
 include $(BUILD_PACKAGE)
-
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
new file mode 100644
index 0000000..6439b49
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="16dip"
+        android:paddingEnd="16dip"
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:orientation="horizontal"
+        android:gravity="start|center_vertical">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="32dip"
+        android:layout_height="32dip"
+        android:layout_gravity="center_vertical"
+        android:layout_marginEnd="8dip"
+        android:duplicateParentState="true"
+        android:contentDescription="@null"
+        android:visibility="gone">
+    </ImageView>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textIsSelectable="false"
+            android:gravity="top|start"
+            android:textColor="@color/item_text_color"
+            android:duplicateParentState="true">
+        </TextView>
+
+        <TextView
+            android:id="@+id/subtitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textIsSelectable="false"
+            android:visibility="gone"
+            android:textColor="@color/print_option_title"
+            android:duplicateParentState="true">
+        </TextView>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 9fe7e00..5ee8d8c 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -109,6 +109,9 @@
     <!-- Label for an unknown reason for failed or blocked print job. [CHAR LIMIT=25] -->
     <string name="reason_unknown">unknown</string>
 
+    <!-- Label for a printer that is not available. [CHAR LIMIT=25] -->
+    <string name="printer_unavailable"><xliff:g id="print_job_name" example="Canon-123GHT">%1$s</xliff:g> &#8211; unavailable</string>
+
     <!-- Arrays -->
 
     <!-- Color mode labels. -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
index 3a1a3c40..65af830 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
@@ -19,13 +19,16 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Loader;
+import android.content.pm.ServiceInfo;
 import android.os.AsyncTask;
 import android.print.PrintManager;
 import android.print.PrinterDiscoverySession;
 import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
+import android.printservice.PrintServiceInfo;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Slog;
@@ -46,6 +49,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import libcore.io.IoUtils;
 
@@ -384,6 +388,30 @@
                             + FusedPrintersProvider.this.hashCode());
                 }
 
+                // Ignore printer records whose target services are not installed.
+                PrintManager printManager = (PrintManager) getContext()
+                        .getSystemService(Context.PRINT_SERVICE);
+                List<PrintServiceInfo> services = printManager
+                        .getInstalledPrintServices();
+
+                Set<ComponentName> installedComponents = new ArraySet<ComponentName>();
+                final int installedServiceCount = services.size();
+                for (int i = 0; i < installedServiceCount; i++) {
+                    ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo;
+                    ComponentName componentName = new ComponentName(
+                            serviceInfo.packageName, serviceInfo.name);
+                    installedComponents.add(componentName);
+                }
+
+                final int printerCount = printers.size();
+                for (int i = printerCount - 1; i >= 0; i--) {
+                    ComponentName printerServiceName = printers.get(i).getId().getServiceName();
+                    if (!installedComponents.contains(printerServiceName.getPackageName())) {
+                        printers.remove(i);
+                    }
+                }
+
+                // Store the filtered list.
                 mHistoricalPrinters = printers;
 
                 // Compute the favorite printers.
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index 8d11a93..44362d4 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -75,11 +75,10 @@
 import android.widget.BaseAdapter;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.ImageView;
 import android.widget.Spinner;
 import android.widget.TextView;
 
-import libcore.io.IoUtils;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -96,6 +95,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import libcore.io.IoUtils;
+
 /**
  * Activity for configuring a print job.
  */
@@ -818,8 +819,6 @@
 
         private PrinterInfo mCurrentPrinter;
 
-        private boolean mRequestedCurrentPrinterRefresh;
-
         private final OnItemSelectedListener mOnItemSelectedListener =
                 new AdapterView.OnItemSelectedListener() {
             @Override
@@ -839,7 +838,7 @@
                         return;
                     }
 
-                    mRequestedCurrentPrinterRefresh = false;
+                    mCapabilitiesTimeout.remove();
 
                     mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter
                             .getItem(position);
@@ -854,8 +853,7 @@
 
                     PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
                     if (capabilities == null) {
-                        // TODO: We need a timeout for the update.
-                        mRequestedCurrentPrinterRefresh = true;
+                        mCapabilitiesTimeout.post();
                         updateUi();
                         refreshCurrentPrinter();
                     } else {
@@ -1128,6 +1126,9 @@
             }
         };
 
+        private final WaitForPrinterCapabilitiesTimeout mCapabilitiesTimeout =
+                new WaitForPrinterCapabilitiesTimeout();
+
         private int mEditorState;
 
         private boolean mIgnoreNextDestinationChange;
@@ -1173,16 +1174,16 @@
                                 if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
                                         && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
                                         && printer.getCapabilities() == null
-                                        && !mRequestedCurrentPrinterRefresh) {
-                                    mRequestedCurrentPrinterRefresh = true;
+                                        && !mCapabilitiesTimeout.isPosted()) {
+                                    mCapabilitiesTimeout.post();
                                     refreshCurrentPrinter();
                                     return;
                                 }
 
                                 // We just refreshed the current printer.
                                 if (printer.getCapabilities() != null
-                                        && mRequestedCurrentPrinterRefresh) {
-                                    mRequestedCurrentPrinterRefresh = false;
+                                        && mCapabilitiesTimeout.isPosted()) {
+                                    mCapabilitiesTimeout.remove();
                                     updatePrintAttributes(printer.getCapabilities());
                                     updateUi();
                                     mController.update();
@@ -1971,6 +1972,43 @@
             }
         }
 
+        private final class WaitForPrinterCapabilitiesTimeout implements Runnable {
+            private static final long GET_CAPABILITIES_TIMEOUT_MILLIS = 10000; // 10sec
+
+            private boolean mIsPosted;
+
+            public void post() {
+                if (!mIsPosted) {
+                    mDestinationSpinner.postDelayed(this,
+                            GET_CAPABILITIES_TIMEOUT_MILLIS);
+                    mIsPosted = true;
+                }
+            }
+
+            public void remove() {
+                if (mIsPosted) {
+                    mIsPosted = false;
+                    mDestinationSpinner.removeCallbacks(this);
+                }
+            }
+
+            public boolean isPosted() {
+                return mIsPosted;
+            }
+
+            @Override
+            public void run() {
+                mIsPosted = false;
+                if (mDestinationSpinner.getSelectedItemPosition() >= 0) {
+                    View itemView = mDestinationSpinner.getSelectedView();
+                    TextView titleView = (TextView) itemView.findViewById(R.id.title);
+                    String title = getString(R.string.printer_unavailable,
+                            mCurrentPrinter.getName());
+                    titleView.setText(title);
+                }
+            }
+        }
+
         private final class DestinationAdapter extends BaseAdapter
                 implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{
             private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
@@ -2066,11 +2104,12 @@
             public View getView(int position, View convertView, ViewGroup parent) {
                 if (convertView == null) {
                     convertView = getLayoutInflater().inflate(
-                            R.layout.spinner_dropdown_item, parent, false);
+                            R.layout.printer_dropdown_item, parent, false);
                 }
 
                 CharSequence title = null;
                 CharSequence subtitle = null;
+                Drawable icon = null;
 
                 if (mPrinters.isEmpty()) {
                     if (position == 0) {
@@ -2092,6 +2131,7 @@
                             PackageInfo packageInfo = getPackageManager().getPackageInfo(
                                     printer.getId().getServiceName().getPackageName(), 0);
                             subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
+                            icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
                         } catch (NameNotFoundException nnfe) {
                             /* ignore */
                         }
@@ -2110,6 +2150,14 @@
                     subtitleView.setVisibility(View.GONE);
                 }
 
+                ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
+                if (icon != null) {
+                    iconView.setImageDrawable(icon);
+                    iconView.setVisibility(View.VISIBLE);
+                } else {
+                    iconView.setVisibility(View.GONE);
+                }
+
                 return convertView;
             }
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index 62b35fe..87181f7 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -43,6 +43,7 @@
 import android.print.PrinterId;
 import android.print.PrinterInfo;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Slog;
@@ -59,10 +60,12 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -82,6 +85,8 @@
 
     private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
 
+    private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
+
     private static final String PRINT_FILE_EXTENSION = "pdf";
 
     private static final Object sLock = new Object();
@@ -168,9 +173,9 @@
                         PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
                         | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
 
-                Message message = mHandlerCaller.obtainMessageIIO(
+                Message message = mHandlerCaller.obtainMessageO(
                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
-                        printJob.getAppId(), 0, printJob.getId());
+                        printJob);
                 mHandlerCaller.executeOrSendMessage(message);
 
                 message = mHandlerCaller.obtainMessageOO(
@@ -179,9 +184,6 @@
                 mHandlerCaller.executeOrSendMessage(message);
 
                 printJob.setCreationTime(System.currentTimeMillis());
-                synchronized (mLock) {
-                    mPersistanceManager.writeStateLocked();
-                }
             }
 
             @Override
@@ -225,12 +227,40 @@
             }
 
             @Override
-            public void forgetPrintJobs(List<PrintJobId> printJobIds) {
-                PrintSpoolerService.this.forgetPrintJobs(printJobIds);
+            protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+                PrintSpoolerService.this.dump(fd, writer, args);
             }
         };
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        synchronized (mLock) {
+            String prefix = args[0];
+            String tab = "  ";
+
+            pw.append(prefix).append("print jobs:").println();
+            final int printJobCount = mPrintJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo printJob = mPrintJobs.get(i);
+                pw.append(prefix).append(tab).append(printJob.toString());
+                pw.println();
+            }
+
+            pw.append(prefix).append("print job files:").println();
+            File[] files = getFilesDir().listFiles();
+            if (files != null) {
+                final int fileCount = files.length;
+                for (int i = 0; i < fileCount; i++) {
+                    File file = files[i];
+                    if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
+                        pw.append(prefix).append(tab).append(file.getName()).println();
+                    }
+                }
+            }
+        }
+    }
+
     private void sendOnPrintJobQueued(PrintJobInfo printJob) {
         Message message = mHandlerCaller.obtainMessageO(
                 HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
@@ -324,10 +354,9 @@
 
                 case MSG_ON_PRINT_JOB_STATE_CHANGED: {
                     if (mClient != null) {
-                        PrintJobId printJobId = (PrintJobId) message.obj;
-                        final int appId = message.arg1;
+                        PrintJobInfo printJob = (PrintJobInfo) message.obj;
                         try {
-                            mClient.onPrintJobStateChanged(printJobId, appId);
+                            mClient.onPrintJobStateChanged(printJob);
                         } catch (RemoteException re) {
                             Slog.e(LOG_TAG, "Error notify for print job state change.", re);
                         }
@@ -391,17 +420,46 @@
     public void createPrintJob(PrintJobInfo printJob) {
         synchronized (mLock) {
             addPrintJobLocked(printJob);
+            setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
         }
     }
 
     private void handleReadPrintJobsLocked() {
+        // Make a map with the files for a print job since we may have
+        // to delete some. One example of getting orphan files if the
+        // spooler crashes while constructing a print job. We do not
+        // persist partially populated print jobs under construction to
+        // avoid special handling for various attributes missing.
+        ArrayMap<PrintJobId, File> fileForJobMap = null;
+        File[] files = getFilesDir().listFiles();
+        if (files != null) {
+            final int fileCount = files.length;
+            for (int i = 0; i < fileCount; i++) {
+                File file = files[i];
+                if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
+                    if (fileForJobMap == null) {
+                        fileForJobMap = new ArrayMap<PrintJobId, File>();
+                    }
+                    String printJobIdString = file.getName().substring(0,
+                            PRINT_JOB_FILE_PREFIX.length());
+                    PrintJobId printJobId = PrintJobId.unflattenFromString(
+                            printJobIdString);
+                    fileForJobMap.put(printJobId, file);
+                }
+            }
+        }
+
         final int printJobCount = mPrintJobs.size();
         for (int i = 0; i < printJobCount; i++) {
             PrintJobInfo printJob = mPrintJobs.get(i);
 
+            // We want to have only the orphan files at the end.
+            if (fileForJobMap != null) {
+                fileForJobMap.remove(printJob.getId());
+            }
+
             // Update the notification.
             mNotificationController.onPrintJobStateChanged(printJob);
-
             switch (printJob.getState()) {
                 case PrintJobInfo.STATE_QUEUED:
                 case PrintJobInfo.STATE_STARTED:
@@ -415,6 +473,15 @@
                 } break;
             }
         }
+
+        // Delete the orphan files.
+        if (fileForJobMap != null) {
+            final int orphanFileCount = fileForJobMap.size();
+            for (int i = 0; i < orphanFileCount; i++) {
+                File file = fileForJobMap.valueAt(i);
+                file.delete();
+            }
+        }
     }
 
     public void checkAllPrintJobsHandled() {
@@ -465,7 +532,7 @@
     }
 
     public File generateFileForPrintJob(PrintJobId printJobId) {
-        return new File(getFilesDir(), "print_job_"
+        return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
                 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
     }
 
@@ -476,31 +543,6 @@
         }
     }
 
-    private void forgetPrintJobs(List<PrintJobId> printJobIds) {
-        synchronized (mLock) {
-            boolean printJobsRemoved = false;
-            final int removedPrintJobCount = printJobIds.size();
-            for (int i = 0; i < removedPrintJobCount; i++) {
-                PrintJobId removedPrintJobId = printJobIds.get(i);
-                final int printJobCount = mPrintJobs.size();
-                for (int j = printJobCount - 1; j >= 0; j--) {
-                    PrintJobInfo printJob = mPrintJobs.get(j);
-                    if (removedPrintJobId.equals(printJob.getId())) {
-                        mPrintJobs.remove(j);
-                        printJobsRemoved = true;
-                        if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                            Slog.i(LOG_TAG, "[FORGOT] " + printJob.getId().flattenToString());
-                        }
-                        removePrintJobFileLocked(printJob.getId());
-                    }
-                }
-            }
-            if (printJobsRemoved) {
-                mPersistanceManager.writeStateLocked();
-            }
-        }
-    }
-
     private void removeObsoletePrintJobs() {
         synchronized (mLock) {
             final int printJobCount = mPrintJobs.size();
@@ -523,7 +565,7 @@
         if (file.exists()) {
             file.delete();
             if (DEBUG_PRINT_JOB_LIFECYCLE) {
-                Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId.flattenToString());
+                Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
             }
         }
     }
@@ -552,10 +594,7 @@
                 switch (state) {
                     case PrintJobInfo.STATE_COMPLETED:
                     case PrintJobInfo.STATE_CANCELED:
-                        // Just remove the file but keep the print job info since
-                        // the app that created it may be holding onto the PrintJob
-                        // instance and query it for its most recent state. We will
-                        // remove the info for this job when told so by the system.
+                        mPrintJobs.remove(printJob);
                         removePrintJobFileLocked(printJob.getId());
                         // $fall-through$
 
@@ -582,9 +621,9 @@
                     notifyOnAllPrintJobsHandled();
                 }
 
-                Message message = mHandlerCaller.obtainMessageIIO(
+                Message message = mHandlerCaller.obtainMessageO(
                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
-                        printJob.getAppId(), 0, printJob.getId());
+                        printJob);
                 mHandlerCaller.executeOrSendMessage(message);
             }
         }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index fb73d39..260a3be 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -197,9 +197,10 @@
             android:name=".DessertCase"
             android:exported="true"
             android:label="@string/dessert_case"
-            android:theme="@android:style/Theme.Wallpaper.NoTitleBar.Fullscreen"
+            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
             android:hardwareAccelerated="true"
             android:launchMode="singleInstance"
+            android:configChanges="orientation|screenSize"
             android:excludeFromRecents="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_android.png b/packages/SystemUI/res/drawable-nodpi/dessert_android.png
new file mode 100644
index 0000000..2b47c19
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_android.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_cupcake.png b/packages/SystemUI/res/drawable-nodpi/dessert_cupcake.png
new file mode 100644
index 0000000..7b48c10
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_cupcake.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_dandroid.png b/packages/SystemUI/res/drawable-nodpi/dessert_dandroid.png
new file mode 100644
index 0000000..8be85c5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_dandroid.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_donut.png b/packages/SystemUI/res/drawable-nodpi/dessert_donut.png
new file mode 100644
index 0000000..167ced7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_donut.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_donutburger.png b/packages/SystemUI/res/drawable-nodpi/dessert_donutburger.png
new file mode 100644
index 0000000..9d77518
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_donutburger.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_eclair.png b/packages/SystemUI/res/drawable-nodpi/dessert_eclair.png
new file mode 100644
index 0000000..8d463eb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_eclair.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_flan.png b/packages/SystemUI/res/drawable-nodpi/dessert_flan.png
new file mode 100644
index 0000000..d05e3de
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_flan.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_froyo.png b/packages/SystemUI/res/drawable-nodpi/dessert_froyo.png
new file mode 100644
index 0000000..ffd9994
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_froyo.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_gingerbread.png b/packages/SystemUI/res/drawable-nodpi/dessert_gingerbread.png
new file mode 100644
index 0000000..22bffbb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_gingerbread.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_honeycomb.png b/packages/SystemUI/res/drawable-nodpi/dessert_honeycomb.png
new file mode 100644
index 0000000..0f51a43
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_honeycomb.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_ics.png b/packages/SystemUI/res/drawable-nodpi/dessert_ics.png
new file mode 100644
index 0000000..bdec60e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_ics.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_jandycane.png b/packages/SystemUI/res/drawable-nodpi/dessert_jandycane.png
new file mode 100644
index 0000000..ba1c7eb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_jandycane.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_jellybean.png b/packages/SystemUI/res/drawable-nodpi/dessert_jellybean.png
new file mode 100644
index 0000000..5a2bcaa
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_jellybean.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_keylimepie.png b/packages/SystemUI/res/drawable-nodpi/dessert_keylimepie.png
new file mode 100644
index 0000000..a8741ec
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_keylimepie.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_kitkat.png b/packages/SystemUI/res/drawable-nodpi/dessert_kitkat.png
new file mode 100644
index 0000000..4f2b03b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_kitkat.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_petitfour.png b/packages/SystemUI/res/drawable-nodpi/dessert_petitfour.png
new file mode 100644
index 0000000..3dc9d95
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_petitfour.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_zombiegingerbread.png b/packages/SystemUI/res/drawable-nodpi/dessert_zombiegingerbread.png
new file mode 100644
index 0000000..7962c21
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_zombiegingerbread.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/jandycane.png b/packages/SystemUI/res/drawable-nodpi/jandycane.png
deleted file mode 100644
index 278cfec..0000000
--- a/packages/SystemUI/res/drawable-nodpi/jandycane.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f5356a8..cc78cb4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -217,4 +217,7 @@
 
     <!-- The width of the notification panel window: match_parent below sw600dp -->
     <dimen name="notification_panel_width">-1dp</dimen>
+
+    <!-- used by DessertCase -->
+    <dimen name="dessert_case_cell_size">192dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCase.java b/packages/SystemUI/src/com/android/systemui/DessertCase.java
index b6424af0..dd4c018 100644
--- a/packages/SystemUI/src/com/android/systemui/DessertCase.java
+++ b/packages/SystemUI/src/com/android/systemui/DessertCase.java
@@ -16,22 +16,51 @@
 
 package com.android.systemui;
 
+import android.animation.ObjectAnimator;
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.os.Handler;
 import android.util.Slog;
+import android.view.animation.DecelerateInterpolator;
 
 public class DessertCase extends Activity {
+    DessertCaseView mView;
 
     @Override
     public void onStart() {
         super.onStart();
 
-        Slog.v("DessertCase", "ACHIEVEMENT UNLOCKED");
         PackageManager pm = getPackageManager();
-        pm.setComponentEnabledSetting(new ComponentName(this, DessertCaseDream.class),
-                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+        final ComponentName cn = new ComponentName(this, DessertCaseDream.class);
+        if (pm.getComponentEnabledSetting(cn) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+            Slog.v("DessertCase", "ACHIEVEMENT UNLOCKED");
+            pm.setComponentEnabledSetting(cn,
+                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+        }
 
-        finish();
+        mView = new DessertCaseView(this);
+
+        DessertCaseView.RescalingContainer container = new DessertCaseView.RescalingContainer(this);
+
+        container.setView(mView);
+
+        setContentView(container);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mView.postDelayed(new Runnable() {
+            public void run() {
+                mView.start();
+            }
+        }, 1000);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mView.stop();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseDream.java b/packages/SystemUI/src/com/android/systemui/DessertCaseDream.java
index 022e4d8..a627cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/DessertCaseDream.java
+++ b/packages/SystemUI/src/com/android/systemui/DessertCaseDream.java
@@ -19,21 +19,36 @@
 import android.service.dreams.DreamService;
 
 public class DessertCaseDream extends DreamService {
+    private DessertCaseView mView;
+    private DessertCaseView.RescalingContainer mContainer;
 
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
-        setInteractive(true);
-        setFullscreen(true);
+        setInteractive(false);
+
+        mView = new DessertCaseView(this);
+
+        mContainer = new DessertCaseView.RescalingContainer(this);
+
+        mContainer.setView(mView);
+
+        setContentView(mContainer);
     }
 
     @Override
     public void onDreamingStarted() {
         super.onDreamingStarted();
+        mView.postDelayed(new Runnable() {
+            public void run() {
+                mView.start();
+            }
+        }, 1000);
     }
 
     @Override
     public void onDreamingStopped() {
         super.onDreamingStopped();
+        mView.stop();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
new file mode 100644
index 0000000..99c59d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.*;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnticipateOvershootInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class DessertCaseView extends FrameLayout {
+    private static final String TAG = DessertCaseView.class.getSimpleName();
+
+    private static final boolean DEBUG = false;
+
+    static final int START_DELAY = 5000;
+    static final int DELAY = 2000;
+    static final int DURATION = 500;
+
+    private static final int TAG_POS = 0x2000001;
+    private static final int TAG_SPAN = 0x2000002;
+
+    private static final int[] PASTRIES = {
+            R.drawable.dessert_kitkat,      // used with permission
+            R.drawable.dessert_android,     // thx irina
+    };
+
+    private static final int[] RARE_PASTRIES = {
+            R.drawable.dessert_cupcake,     // 2009
+            R.drawable.dessert_donut,       // 2009
+            R.drawable.dessert_eclair,      // 2009
+            R.drawable.dessert_froyo,       // 2010
+            R.drawable.dessert_gingerbread, // 2010
+            R.drawable.dessert_honeycomb,   // 2011
+            R.drawable.dessert_ics,         // 2011
+            R.drawable.dessert_jellybean,   // 2012
+    };
+
+    private static final int[] XRARE_PASTRIES = {
+            R.drawable.dessert_petitfour,   // the original and still delicious
+
+            R.drawable.dessert_donutburger, // remember kids, this was long before cronuts
+
+            R.drawable.dessert_flan,        //     sholes final approach
+                                            //     landing gear punted to flan
+                                            //     runway foam glistens
+                                            //         -- mcleron
+
+            R.drawable.dessert_keylimepie,  // from an alternative timeline
+    };
+    private static final int[] XXRARE_PASTRIES = {
+            R.drawable.dessert_zombiegingerbread, // thx hackbod
+            R.drawable.dessert_dandroid,    // thx morrildl
+            R.drawable.dessert_jandycane,   // thx nes
+    };
+
+    private static final int NUM_PASTRIES = PASTRIES.length + RARE_PASTRIES.length
+            + XRARE_PASTRIES.length + XXRARE_PASTRIES.length;
+
+    private SparseArray<Drawable> mDrawables = new SparseArray<Drawable>(NUM_PASTRIES);
+
+    private static final float[] MASK = {
+            0f,  0f,  0f,  0f, 255f,
+            0f,  0f,  0f,  0f, 255f,
+            0f,  0f,  0f,  0f, 255f,
+            1f,  0f,  0f,  0f, 0f
+    };
+
+    private static final float[] WHITE_MASK = {
+            0f,  0f,  0f,  0f, 255f,
+            0f,  0f,  0f,  0f, 255f,
+            0f,  0f,  0f,  0f, 255f,
+            -1f,  0f,  0f,  0f, 255f
+    };
+
+    public static final float SCALE = 0.25f; // natural display size will be SCALE*mCellSize
+
+    private static final float PROB_2X = 0.33f;
+    private static final float PROB_3X = 0.1f;
+    private static final float PROB_4X = 0.01f;
+
+    private boolean mStarted;
+
+    private int mCellSize;
+    private int mWidth, mHeight;
+    private int mRows, mColumns;
+    private View[] mCells;
+
+    private final Set<Point> mFreeList = new HashSet<Point>();
+
+    private final Handler mHandler = new Handler();
+
+    private final Runnable mJuggle = new Runnable() {
+        @Override
+        public void run() {
+            final int N = getChildCount();
+
+            final int K = 1; //irand(1,3);
+            for (int i=0; i<K; i++) {
+                final View child = getChildAt((int) (Math.random() * N));
+                place(child, true);
+            }
+
+            fillFreeList();
+
+            if (mStarted) {
+                mHandler.postDelayed(mJuggle, DELAY);
+            }
+        }
+    };
+
+    public DessertCaseView(Context context) {
+        this(context, null);
+    }
+
+    public DessertCaseView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DessertCaseView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final Resources res = getResources();
+
+        mStarted = false;
+
+        mCellSize = res.getDimensionPixelSize(R.dimen.dessert_case_cell_size);
+        final BitmapFactory.Options opts = new BitmapFactory.Options();
+        if (mCellSize < 512) { // assuming 512x512 images
+            opts.inSampleSize = 2;
+        }
+        for (int[] list : new int[][] { PASTRIES, RARE_PASTRIES, XRARE_PASTRIES, XXRARE_PASTRIES }) {
+            for (int resid : list) {
+                final BitmapDrawable d = new BitmapDrawable(res,
+                        BitmapFactory.decodeResource(res, resid, opts));
+                d.setColorFilter(new ColorMatrixColorFilter(MASK));
+                d.setBounds(0, 0, mCellSize, mCellSize);
+                mDrawables.append(resid, d);
+            }
+        }
+        if (DEBUG) setWillNotDraw(false);
+    }
+
+    public void start() {
+        if (!mStarted) {
+            mStarted = true;
+            fillFreeList(DURATION * 4);
+        }
+        mHandler.postDelayed(mJuggle, START_DELAY);
+    }
+
+    public void stop() {
+        mStarted = false;
+        mHandler.removeCallbacks(mJuggle);
+    }
+
+    int pick(int[] a) {
+        return a[(int)(Math.random()*a.length)];
+    }
+
+    <T> T pick(T[] a) {
+        return a[(int)(Math.random()*a.length)];
+    }
+
+    <T> T pick(SparseArray<T> sa) {
+        return sa.valueAt((int)(Math.random()*sa.size()));
+    }
+
+    float[] hsv = new float[] { 0, 1f, .85f };
+    int random_color() {
+//        return 0xFF000000 | (int) (Math.random() * (float) 0xFFFFFF); // totally random
+        final int COLORS = 12;
+        hsv[0] = irand(0,COLORS) * (360f/COLORS);
+        return Color.HSVToColor(hsv);
+    }
+
+    @Override
+    protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        if (mWidth == w && mHeight == h) return;
+
+        final boolean wasStarted = mStarted;
+        if (wasStarted) {
+            stop();
+        }
+
+        mWidth = w;
+        mHeight = h;
+
+        mCells = null;
+        removeAllViewsInLayout();
+        mFreeList.clear();
+
+        mRows = mHeight / mCellSize;
+        mColumns = mWidth / mCellSize;
+
+        mCells = new View[mRows * mColumns];
+
+        if (DEBUG) Log.v(TAG, String.format("New dimensions: %dx%d", mColumns, mRows));
+
+        setScaleX(SCALE);
+        setScaleY(SCALE);
+        setTranslationX(0.5f * (mWidth - mCellSize * mColumns) * SCALE);
+        setTranslationY(0.5f * (mHeight - mCellSize * mRows) * SCALE);
+
+        for (int j=0; j<mRows; j++) {
+            for (int i=0; i<mColumns; i++) {
+                mFreeList.add(new Point(i,j));
+            }
+        }
+
+        if (wasStarted) {
+            start();
+        }
+    }
+
+    public void fillFreeList() {
+        fillFreeList(DURATION);
+    }
+
+    public synchronized void fillFreeList(int animationLen) {
+        final Context ctx = getContext();
+        final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mCellSize, mCellSize);
+
+        while (! mFreeList.isEmpty()) {
+            Point pt = mFreeList.iterator().next();
+            mFreeList.remove(pt);
+            final int i=pt.x;
+            final int j=pt.y;
+
+            if (mCells[j*mColumns+i] != null) continue;
+            final ImageView v = new ImageView(ctx);
+            v.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    place(v, true);
+                    postDelayed(new Runnable() { public void run() { fillFreeList(); } }, DURATION/2);
+                }
+            });
+
+            final int c = random_color();
+            v.setBackgroundColor(c);
+
+            final float which = frand();
+            final Drawable d;
+            if (which < 0.001f) {
+                d = mDrawables.get(pick(XXRARE_PASTRIES));
+            } else if (which < 0.01f) {
+                d = mDrawables.get(pick(XRARE_PASTRIES));
+            } else if (which < 0.5f) {
+                d = mDrawables.get(pick(RARE_PASTRIES));
+            } else if (which < 0.7f) {
+                d = mDrawables.get(pick(PASTRIES));
+            } else {
+                d = null;
+            }
+            if (d != null) {
+                v.getOverlay().add(d);
+            }
+
+            final Paint paint = new Paint();
+            v.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
+
+            lp.width = lp.height = mCellSize;
+            addView(v, lp);
+            place(v, pt, false);
+            if (animationLen > 0) {
+                final float s = (Integer) v.getTag(TAG_SPAN);
+                v.setScaleX(0.5f * s);
+                v.setScaleY(0.5f * s);
+                v.setAlpha(0f);
+                v.animate().scaleX(s).scaleY(s).alpha(1f).setDuration(animationLen);
+            }
+        }
+    }
+
+    public void place(View v, boolean animate) {
+        place(v, new Point(irand(0, mColumns), irand(0, mRows)), animate);
+    }
+
+    private final HashSet<View> tmpSet = new HashSet<View>();
+    public synchronized void place(View v, Point pt, boolean animate) {
+        final int i = pt.x;
+        final int j = pt.y;
+        final float rnd = frand();
+        if (v.getTag(TAG_POS) != null) {
+            for (final Point oc : getOccupied(v)) {
+                mFreeList.add(oc);
+                mCells[oc.y*mColumns + oc.x] = null;
+            }
+        }
+        int scale = 1;
+        if (rnd < PROB_4X) {
+            if (!(i >= mColumns-3 || j >= mRows-3)) {
+                scale = 4;
+            }
+        } else if (rnd < PROB_3X) {
+            if (!(i >= mColumns-2 || j >= mRows-2)) {
+                scale = 3;
+            }
+        } else if (rnd < PROB_2X) {
+            if (!(i == mColumns-1 || j == mRows-1)) {
+                scale = 2;
+            }
+        }
+
+        v.setTag(TAG_POS, pt);
+        v.setTag(TAG_SPAN, scale);
+
+        tmpSet.clear();
+
+        final Point[] occupied = getOccupied(v);
+        for (final Point oc : occupied) {
+            final View squatter = mCells[oc.y*mColumns + oc.x];
+            if (squatter != null) {
+                tmpSet.add(squatter);
+            }
+        }
+
+        for (final View squatter : tmpSet) {
+            for (final Point sq : getOccupied(squatter)) {
+                mFreeList.add(sq);
+                mCells[sq.y*mColumns + sq.x] = null;
+            }
+            if (squatter != v) {
+                squatter.setTag(TAG_POS, null);
+                if (animate) {
+                    squatter.animate().scaleX(0.5f).scaleY(0.5f).alpha(0)
+                            .setDuration(DURATION)
+                            .setInterpolator(new AccelerateInterpolator())
+                            .setListener(new Animator.AnimatorListener() {
+                                public void onAnimationStart(Animator animator) { }
+                                public void onAnimationEnd(Animator animator) {
+                                    removeView(squatter);
+                                }
+                                public void onAnimationCancel(Animator animator) { }
+                                public void onAnimationRepeat(Animator animator) { }
+                            })
+                            .start();
+                } else {
+                    removeView(squatter);
+                }
+            }
+        }
+
+        for (final Point oc : occupied) {
+            mCells[oc.y*mColumns + oc.x] = v;
+            mFreeList.remove(oc);
+        }
+
+        final float rot = (float)irand(0, 4) * 90f;
+
+        if (animate) {
+            v.bringToFront();
+            AnimatorSet set1 = new AnimatorSet();
+            set1.playTogether(
+                    ObjectAnimator.ofFloat(v, View.SCALE_X, (float) scale),
+                    ObjectAnimator.ofFloat(v, View.SCALE_Y, (float) scale)
+            );
+            set1.setInterpolator(new AnticipateOvershootInterpolator());
+            set1.setDuration(DURATION);
+            set1.start();
+
+            AnimatorSet set2 = new AnimatorSet();
+            set2.playTogether(
+                    ObjectAnimator.ofFloat(v, View.ROTATION, rot),
+                    ObjectAnimator.ofFloat(v, View.X, i* mCellSize + (scale-1) * mCellSize /2),
+                    ObjectAnimator.ofFloat(v, View.Y, j* mCellSize + (scale-1) * mCellSize /2)
+            );
+            set2.setInterpolator(new DecelerateInterpolator());
+            set2.setDuration(DURATION);
+            set2.start();
+        } else {
+            v.setX(i * mCellSize + (scale-1) * mCellSize /2);
+            v.setY(j * mCellSize + (scale-1) * mCellSize /2);
+            v.setScaleX((float) scale);
+            v.setScaleY((float) scale);
+            v.setRotation(rot);
+        }
+    }
+
+    private Point[] getOccupied(View v) {
+        final int scale = (Integer) v.getTag(TAG_SPAN);
+        final Point pt = (Point)v.getTag(TAG_POS);
+        if (pt == null || scale == 0) return new Point[0];
+
+        final Point[] result = new Point[scale * scale];
+        int p=0;
+        for (int i=0; i<scale; i++) {
+            for (int j=0; j<scale; j++) {
+                result[p++] = new Point(pt.x + i, pt.y + j);
+            }
+        }
+        return result;
+    }
+
+    static float frand() {
+        return (float)(Math.random());
+    }
+
+    static float frand(float a, float b) {
+        return (frand() * (b-a) + a);
+    }
+
+    static int irand(int a, int b) {
+        return (int)(frand(a, b));
+    }
+
+    @Override
+    public void onDraw(Canvas c) {
+        super.onDraw(c);
+        if (!DEBUG) return;
+
+        Paint pt = new Paint();
+        pt.setStyle(Paint.Style.STROKE);
+        pt.setColor(0xFFCCCCCC);
+        pt.setStrokeWidth(2.0f);
+
+        final Rect check = new Rect();
+        final int N = getChildCount();
+        for (int i = 0; i < N; i++) {
+            View stone = getChildAt(i);
+
+            stone.getHitRect(check);
+
+            c.drawRect(check, pt);
+        }
+    }
+
+    public static class RescalingContainer extends FrameLayout {
+        private static final int SYSTEM_UI_MODE_800 = 0x00000800;
+        private DessertCaseView mView;
+        private float mDarkness;
+
+        public RescalingContainer(Context context) {
+            super(context);
+
+            setSystemUiVisibility(0
+                    | View.SYSTEM_UI_FLAG_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                    | SYSTEM_UI_MODE_800
+            );
+        }
+
+        public void setView(DessertCaseView v) {
+            addView(v);
+            mView = v;
+        }
+
+        @Override
+        protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
+            final float w = right-left;
+            final float h = bottom-top;
+            final int w2 = (int) (w / mView.SCALE / 2);
+            final int h2 = (int) (h / mView.SCALE / 2);
+            final int cx = (int) (left + w * 0.5f);
+            final int cy = (int) (top + h * 0.5f);
+            mView.layout(cx - w2, cy - h2, cx + w2, cy + h2);
+        }
+
+        public void setDarkness(float p) {
+            mDarkness = p;
+            getDarkness();
+            final int x = (int) (p * 0xff);
+            setBackgroundColor(x << 24 & 0xFF000000);
+        }
+
+        public float getDarkness() {
+            return mDarkness;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
index c32f741..c7f0e17 100644
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
@@ -46,6 +46,7 @@
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarPanel;
+import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 public class SearchPanelView extends FrameLayout implements
@@ -88,11 +89,7 @@
 
         if (isKeyguardShowing) {
             // Have keyguard show the bouncer and launch the activity if the user succeeds.
-            try {
-                mWm.showAssistant();
-            } catch (RemoteException e) {
-                // too bad, so sad...
-            }
+            KeyguardTouchDelegate.getInstance(getContext()).showAssistant();
             onAnimationStarted();
         } else {
             // Otherwise, keyguard isn't showing so launch it from here.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java
index 1221a55..5c55f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java
@@ -23,7 +23,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.Log;
+import android.util.Slog;
 import android.view.MotionEvent;
 
 import com.android.internal.policy.IKeyguardExitCallback;
@@ -41,7 +41,9 @@
     static final String KEYGUARD_PACKAGE = "com.android.keyguard";
     static final String KEYGUARD_CLASS = "com.android.keyguard.KeyguardService";
 
-    IKeyguardService mService;
+    private static KeyguardTouchDelegate sInstance;
+
+    private volatile IKeyguardService mService;
 
     protected static final boolean DEBUG = false;
     protected static final String TAG = "KeyguardTouchDelegate";
@@ -49,83 +51,121 @@
     private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.v(TAG, "Connected to keyguard");
+            Slog.v(TAG, "Connected to keyguard");
             mService = IKeyguardService.Stub.asInterface(service);
 
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            Log.v(TAG, "Disconnected from keyguard");
+            Slog.v(TAG, "Disconnected from keyguard");
             mService = null;
+            sInstance = null; // force reconnection if this goes away
         }
 
     };
 
-    public KeyguardTouchDelegate(Context context) {
+    private KeyguardTouchDelegate(Context context) {
         Intent intent = new Intent();
         intent.setClassName(KEYGUARD_PACKAGE, KEYGUARD_CLASS);
         if (!context.bindServiceAsUser(intent, mKeyguardConnection,
                 Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
-            if (DEBUG) Log.v(TAG, "*** Keyguard: can't bind to " + KEYGUARD_CLASS);
+            if (DEBUG) Slog.v(TAG, "*** Keyguard: can't bind to " + KEYGUARD_CLASS);
         } else {
-            if (DEBUG) Log.v(TAG, "*** Keyguard started");
+            if (DEBUG) Slog.v(TAG, "*** Keyguard started");
         }
     }
 
+    public static KeyguardTouchDelegate getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new KeyguardTouchDelegate(context);
+        }
+        return sInstance;
+    }
+
     public boolean isSecure() {
-        boolean secure = false;
-        if (mService != null) {
+        final IKeyguardService service = mService;
+        if (service != null) {
             try {
-                secure = mService.isSecure();
+                return service.isSecure();
             } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException calling keyguard.isSecure()!", e);
+                Slog.e(TAG, "RemoteException calling keyguard.isSecure()!", e);
             }
         } else {
-            Log.w(TAG, "isSecure(): NO SERVICE!");
+            Slog.w(TAG, "isSecure(): NO SERVICE!");
         }
-        return secure;
+        return false;
     }
 
     public boolean dispatch(MotionEvent event) {
-        if (mService != null) {
+        final IKeyguardService service = mService;
+        if (service != null) {
             try {
-                mService.dispatch(event);
+                service.dispatch(event);
+                return true;
             } catch (RemoteException e) {
                 // What to do?
-                Log.e(TAG, "RemoteException sending event to keyguard!", e);
-                return false;
+                Slog.e(TAG, "RemoteException sending event to keyguard!", e);
             }
-            return true;
         } else {
-            Log.w(TAG, "dispatch(event): NO SERVICE!");
+            Slog.w(TAG, "dispatch(event): NO SERVICE!");
+        }
+        return false;
+    }
+
+    public boolean isInputRestricted() {
+        final IKeyguardService service = mService;
+        if (service != null) {
+            try {
+                return service.isInputRestricted();
+            } catch (RemoteException e) {
+                Slog.w(TAG , "Remote Exception", e);
+            }
+        } else {
+            Slog.w(TAG, "isInputRestricted(): NO SERVICE!");
+        }
+        return false;
+    }
+
+    public boolean isShowingAndNotHidden() {
+        final IKeyguardService service = mService;
+        if (service != null) {
+            try {
+                return service.isShowingAndNotHidden();
+            } catch (RemoteException e) {
+                Slog.w(TAG , "Remote Exception", e);
+            }
+        } else {
+            Slog.w(TAG, "isShowingAndNotHidden(): NO SERVICE!");
         }
         return false;
     }
 
     public void showAssistant() {
-        if (mService != null) {
+        final IKeyguardService service = mService;
+        if (service != null) {
             try {
-                mService.showAssistant();
+                service.showAssistant();
             } catch (RemoteException e) {
                 // What to do?
-                Log.e(TAG, "RemoteException launching assistant!", e);
+                Slog.e(TAG, "RemoteException launching assistant!", e);
             }
         } else {
-            Log.w(TAG, "dispatch(event): NO SERVICE!");
+            Slog.w(TAG, "showAssistant(event): NO SERVICE!");
         }
     }
 
     public void launchCamera() {
-        if (mService != null) {
+        final IKeyguardService service = mService;
+        if (service != null) {
             try {
-                mService.launchCamera();
+                service.launchCamera();
             } catch (RemoteException e) {
                 // What to do?
-                Log.e(TAG, "RemoteException launching camera!", e);
+                Slog.e(TAG, "RemoteException launching camera!", e);
             }
         } else {
-            Log.w(TAG, "dispatch(event): NO SERVICE!");
+            Slog.w(TAG, "dispatch(event): NO SERVICE!");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 596fac6..04885f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -88,7 +88,6 @@
 
     // used to disable the camera icon in navbar when disabled by DPM
     private boolean mCameraDisabledByDpm;
-    KeyguardTouchDelegate mKeyguardTouchDelegate;
 
     private final OnTouchListener mCameraTouchListener = new OnTouchListener() {
         @Override
@@ -112,7 +111,7 @@
                     }
                     break;
             }
-            return mKeyguardTouchDelegate.dispatch(event);
+            return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event);
         }
     };
 
@@ -155,8 +154,6 @@
 
         mBarTransitions = new NavigationBarTransitions(this);
 
-        mKeyguardTouchDelegate = new KeyguardTouchDelegate(mContext);
-
         mCameraDisabledByDpm = isCameraDisabledByDpm();
         watchForDevicePolicyChanges();
     }
@@ -341,7 +338,7 @@
                 final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
                 final  boolean disabledBecauseKeyguardSecure =
                         (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
-                        && mKeyguardTouchDelegate.isSecure();
+                        && KeyguardTouchDelegate.getInstance(getContext()).isSecure();
                 return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
             } catch (RemoteException e) {
                 Log.e(TAG, "Can't get userId", e);
@@ -426,9 +423,9 @@
 
     protected void launchForAccessibilityClick(View v) {
         if (v == getCameraButton()) {
-            mKeyguardTouchDelegate.launchCamera();
+            KeyguardTouchDelegate.getInstance(getContext()).launchCamera();
         } else if (v == getSearchLight()) {
-            mKeyguardTouchDelegate.showAssistant();
+            KeyguardTouchDelegate.getInstance(getContext()).showAssistant();
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 9f9b6d6..a5fd1d7 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -5127,11 +5127,6 @@
     }
 
     @Override
-    public void showAssistant() {
-        mKeyguardDelegate.showAssistant();
-    }
-
-    @Override
     public boolean canMagnifyWindow(int windowType) {
         switch (windowType) {
             case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
index 5e299ee..83be1a8 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
@@ -181,11 +181,7 @@
     }
 
     public void showAssistant() {
-        try {
-            mService.showAssistant();
-        } catch (RemoteException e) {
-            Slog.w(TAG , "Remote Exception", e);
-        }
+        // Not used by PhoneWindowManager
     }
 
     public void dispatch(MotionEvent event) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 92ac30d..ddb6d1a 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -2705,18 +2705,6 @@
         return intent;
     }
 
-    String getHomePackageName() {
-        Intent intent = getHomeIntent();
-        ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, mCurrentUserId);
-        if (aInfo != null) {
-            final String homePackageName = aInfo.applicationInfo.packageName;
-            if (!ResolverActivity.class.getName().equals(homePackageName)) {
-                return homePackageName;
-            }
-        }
-        return null;
-    }
-
     boolean startHomeActivityLocked(int userId) {
         if (mHeadless) {
             // Added because none of the other calls to ensureBootCompleted seem to fire
@@ -6786,6 +6774,10 @@
             // Kill the running processes.
             for (int i=0; i<procs.size(); i++) {
                 ProcessRecord pr = procs.get(i);
+                if (pr == mHomeProcess) {
+                    // Don't kill the home process along with tasks from the same package.
+                    continue;
+                }
                 if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
                     killUnneededProcessLocked(pr, "remove task");
                 } else {
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 4359895..2c0b83b 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -58,6 +58,7 @@
 final class ActivityRecord {
     static final String TAG = ActivityManagerService.TAG;
     static final boolean DEBUG_SAVED_STATE = ActivityStackSupervisor.DEBUG_SAVED_STATE;
+    final public static String RECENTS_PACKAGE_NAME = "com.android.systemui.recent";
 
     final ActivityManagerService service; // owner
     final IApplicationToken.Stub appToken; // window manager token
@@ -443,25 +444,18 @@
             noDisplay = ent != null && ent.array.getBoolean(
                     com.android.internal.R.styleable.Window_windowNoDisplay, false);
 
-            // If we know the system has determined the component, then
-            // we can consider this to be a home activity...
-            String homePackageName = supervisor.getHomePackageName();
-            if (homePackageName != null && homePackageName.equals(packageName)) {
-                mActivityType = HOME_ACTIVITY_TYPE;
-            } else if ((!_componentSpecified || _launchedFromUid == Process.myUid()
+            if ((!_componentSpecified || _launchedFromUid == Process.myUid()
                     || _launchedFromUid == 0) &&
                     Intent.ACTION_MAIN.equals(_intent.getAction()) &&
                     _intent.hasCategory(Intent.CATEGORY_HOME) &&
                     _intent.getCategories().size() == 1 &&
                     _intent.getData() == null &&
                     _intent.getType() == null &&
-                    (intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+                    (intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
+                    isNotResolverActivity()) {
                 // This sure looks like a home activity!
                 mActivityType = HOME_ACTIVITY_TYPE;
-                if (isNotResolverActivity()) {
-                    supervisor.setHomePackageName(userId, packageName);
-                }
-            } else if (realActivity.getClassName().contains("com.android.systemui.recent")) {
+            } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
                 mActivityType = RECENTS_ACTIVITY_TYPE;
             } else {
                 mActivityType = APPLICATION_ACTIVITY_TYPE;
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index a88c3cc..a7fc995 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -334,20 +334,16 @@
         mCurrentUser = service.mCurrentUserId;
     }
 
-    private boolean okToShow(ActivityRecord r) {
+    boolean okToShow(ActivityRecord r) {
         return r.userId == mCurrentUser
                 || (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0;
     }
 
     final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            final TaskRecord task = mTaskHistory.get(taskNdx);
-            final ArrayList<ActivityRecord> activities = task.mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                ActivityRecord r = activities.get(activityNdx);
-                if (!r.finishing && r != notTop && okToShow(r)) {
-                    return r;
-                }
+            ActivityRecord r = mTaskHistory.get(taskNdx).topRunningActivityLocked(notTop);
+            if (r != null) {
+                return r;
             }
         }
         return null;
@@ -937,6 +933,10 @@
         next.idle = false;
         next.results = null;
         next.newIntents = null;
+        if (next.nowVisible) {
+            // We won't get a call to reportActivityVisibleLocked() so dismiss lockscreen now.
+            mStackSupervisor.dismissKeyguard();
+        }
 
         // schedule an idle timeout in case the app doesn't do it for us.
         mStackSupervisor.scheduleIdleTimeoutLocked(next);
@@ -3401,11 +3401,16 @@
         }
     }
 
-    void handleAppDiedLocked(ProcessRecord app, boolean restarting) {
+    /**
+     * Reset local parameters because an app's activity died.
+     * @param app The app of the activity that died.
+     * @return true if home should be launched next.
+     */
+    boolean handleAppDiedLocked(ProcessRecord app) {
         if (!containsApp(app)) {
-            return;
+            return false;
         }
-        // TODO: handle the case where an app spans multiple stacks.
+
         if (mPausingActivity != null && mPausingActivity.app == app) {
             if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG,
                     "App died while pausing: " + mPausingActivity);
@@ -3415,28 +3420,32 @@
             mLastPausedActivity = null;
             mLastNoHistoryActivity = null;
         }
-        final ActivityRecord top = topRunningActivityLocked(null);
-        final boolean launchHomeTaskNext =
-                top != null && top.app == app && top.task.mOnTopOfHome;
 
-        // Remove this application's activities from active lists.
-        boolean hasVisibleActivities = removeHistoryRecordsForAppLocked(app);
-
-        if (!restarting) {
-            ActivityStack stack = mStackSupervisor.getFocusedStack();
-            if (stack == null || launchHomeTaskNext) {
-                mStackSupervisor.resumeHomeActivity(null);
-            } else if (!mStackSupervisor.resumeTopActivitiesLocked(stack, null, null)) {
-                // If there was nothing to resume, and we are not already
-                // restarting this process, but there is a visible activity that
-                // is hosted by the process...  then make sure all visible
-                // activities are running, taking care of restarting this
-                // process.
-                if (hasVisibleActivities) {
-                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
-                }
+        // Determine if the top task is exiting and should return to home. Do this before it gets
+        // removed in removeHistoryRecordsForAppsLocked.
+        boolean launchHomeNext = false;
+        int top = mTaskHistory.size() - 1;
+        while (top >= 0) {
+            final TaskRecord topTask = mTaskHistory.get(top);
+            if (topTask.mActivities.isEmpty()) {
+                // Not possible, but just in case.
+                --top;
+                continue;
             }
+            ActivityRecord r = topTask.topRunningActivityLocked(null);
+            if (r != null) {
+                // r will be launched next.
+                break;
+            }
+            // There is an activity in topTask that is finishing. If topTask belongs to the app
+            // return to home depending on the task flag.
+            launchHomeNext = topTask.mOnTopOfHome;
+            break;
         }
+
+        removeHistoryRecordsForAppLocked(app);
+
+        return launchHomeNext;
     }
 
     void handleAppCrashLocked(ProcessRecord app) {
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 1ee13ec..f718706 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -203,12 +203,6 @@
      */
     final PowerManager.WakeLock mGoingToSleep;
 
-    /**
-     * The name of the current home activity for each user.
-     * TODO: Remove entries when user is deleted.
-     */
-    final SparseArray<String> mHomePackageNames = new SparseArray<String>();
-
     public ActivityStackSupervisor(ActivityManagerService service, Context context,
             Looper looper) {
         mService = service;
@@ -1932,10 +1926,28 @@
     }
 
     void handleAppDiedLocked(ProcessRecord app, boolean restarting) {
-        // Just in case.
+        boolean launchHomeTaskNext = false;
+        final ActivityStack focusedStack = getFocusedStack();
         final int numStacks = mStacks.size();
         for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-            mStacks.get(stackNdx).handleAppDiedLocked(app, restarting);
+            final ActivityStack stack = mStacks.get(stackNdx);
+            // Only update launchHomeTaskNext for the focused stack.
+            launchHomeTaskNext |= (stack == focusedStack && stack.handleAppDiedLocked(app));
+        }
+
+        if (!restarting) {
+            if (launchHomeTaskNext) {
+                resumeHomeActivity(null);
+            } else {
+                if (!resumeTopActivitiesLocked(focusedStack, null, null)) {
+                    // If there was nothing to resume, and we are not already
+                    // restarting this process, but there is a visible activity that
+                    // is hosted by the process...  then make sure all visible
+                    // activities are running, taking care of restarting this
+                    // process.
+                    ensureActivitiesVisibleLocked(null, 0);
+                }
+            }
         }
     }
 
@@ -2267,11 +2279,6 @@
     boolean switchUserLocked(int userId, UserStartedState uss) {
         mCurrentUser = userId;
 
-        final String homePackageName = mService.getHomePackageName();
-        if (homePackageName != null) {
-            setHomePackageName(mCurrentUser, homePackageName);
-        }
-
         mStartingUsers.add(uss);
         boolean haveActivities = false;
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
@@ -2373,12 +2380,6 @@
         pw.print(prefix); pw.print("mStackState="); pw.println(stackStateToString(mStackState));
         pw.print(prefix); pw.println("mSleepTimeout: " + mSleepTimeout);
         pw.print(prefix); pw.println("mCurTaskId: " + mCurTaskId);
-        pw.print(prefix); pw.print("mHomePackageNames:");
-                for (int i = 0; i < mHomePackageNames.size(); ++i) {
-                    pw.print(" ("); pw.print(mHomePackageNames.keyAt(i)); pw.print(",");
-                    pw.print(mHomePackageNames.valueAt(i)); pw.print(")");
-                }
-                pw.println();
     }
 
     ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
@@ -2635,14 +2636,4 @@
             }
         }
     }
-
-    String getHomePackageName() {
-        return mHomePackageNames.get(mCurrentUser);
-    }
-
-    void setHomePackageName(int userId, String homePackageName) {
-        if (DEBUG_SWITCH) Slog.d(TAG, "setHomePackageName: user=" + userId + " package="
-                + homePackageName);
-        mHomePackageNames.put(userId, homePackageName);
-    }
 }
diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java
index f0bba4f..385253e 100644
--- a/services/java/com/android/server/am/TaskRecord.java
+++ b/services/java/com/android/server/am/TaskRecord.java
@@ -139,6 +139,16 @@
         return null;
     }
 
+    ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
+        for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+            ActivityRecord r = mActivities.get(activityNdx);
+            if (!r.finishing && r != notTop && stack.okToShow(r)) {
+                return r;
+            }
+        }
+        return null;
+    }
+
     /**
      * Reorder the history stack so that the activity at the given index is
      * brought to the front.
@@ -418,6 +428,7 @@
             pw.print(prefix); pw.print("numActivities="); pw.print(numActivities);
                     pw.print(" rootWasReset="); pw.print(rootWasReset);
                     pw.print(" userId="); pw.print(userId);
+                    pw.print(" mTaskType="); pw.print(mTaskType);
                     pw.print(" numFullscreen="); pw.print(numFullscreen);
                     pw.print(" mOnTopOfHome="); pw.println(mOnTopOfHome);
         }
diff --git a/services/java/com/android/server/connectivity/PacManager.java b/services/java/com/android/server/connectivity/PacManager.java
index 53e1dc2..1cb2fe3 100644
--- a/services/java/com/android/server/connectivity/PacManager.java
+++ b/services/java/com/android/server/connectivity/PacManager.java
@@ -86,6 +86,9 @@
     private int mCurrentDelay;
     private int mLastPort;
 
+    private boolean mHasSentBroadcast;
+    private boolean mHasDownloaded;
+
     /**
      * Used for locking when setting mProxyService and all references to mPacUrl or mCurrentPac.
      */
@@ -110,6 +113,8 @@
                         setCurrentProxyScript(file);
                     }
                 }
+                mHasDownloaded = true;
+                sendProxyIfNeeded();
                 longSchedule();
             } else {
                 reschedule();
@@ -155,6 +160,8 @@
                 mPacUrl = proxy.getPacFileUrl();
             }
             mCurrentDelay = DELAY_1;
+            mHasSentBroadcast = false;
+            mHasDownloaded = false;
             getAlarmManager().cancel(mPacRefreshIntent);
             bind();
             return true;
@@ -311,10 +318,14 @@
                         callbackService.getProxyPort(new IProxyPortListener.Stub() {
                             @Override
                             public void setProxyPort(int port) throws RemoteException {
+                                if (mLastPort != -1) {
+                                    // Always need to send if port changed
+                                    mHasSentBroadcast = false;
+                                }
                                 mLastPort = port;
                                 if (port != -1) {
                                     Log.d(TAG, "Local proxy is bound on " + port);
-                                    sendPacBroadcast(new ProxyProperties(mPacUrl, port));
+                                    sendProxyIfNeeded();
                                 } else {
                                     Log.e(TAG, "Received invalid port from Local Proxy,"
                                             + " PAC will not be operational");
@@ -341,6 +352,7 @@
             mProxyConnection = null;
         }
         mProxyService = null;
+        mLastPort = -1;
     }
 
     private void sendPacBroadcast(ProxyProperties proxy) {
@@ -355,4 +367,14 @@
             Binder.restoreCallingIdentity(ident);
         }
     }
+
+    private synchronized void sendProxyIfNeeded() {
+        if (!mHasDownloaded || (mLastPort == -1)) {
+            return;
+        }
+        if (!mHasSentBroadcast) {
+            sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort));
+            mHasSentBroadcast = true;
+        }
+    }
 }
diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java
index d4583b5..b8e1b04 100644
--- a/services/java/com/android/server/print/PrintManagerService.java
+++ b/services/java/com/android/server/print/PrintManagerService.java
@@ -194,6 +194,21 @@
     }
 
     @Override
+    public List<PrintServiceInfo> getInstalledPrintServices(int userId) {
+        final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
+        final UserState userState;
+        synchronized (mLock) {
+            userState = getOrCreateUserStateLocked(resolvedUserId);
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return userState.getInstalledPrintServices();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public void createPrinterDiscoverySession(IPrinterDiscoveryObserver observer,
             int userId) {
         final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java
index f98a805..798cea3 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -33,7 +33,6 @@
 import android.print.IPrintSpoolerClient;
 import android.print.PrintJobId;
 import android.print.PrintJobInfo;
-import android.print.PrintManager;
 import android.util.Slog;
 import android.util.TimedRemoteCaller;
 
@@ -91,7 +90,7 @@
     public static interface PrintSpoolerCallbacks {
         public void onPrintJobQueued(PrintJobInfo printJob);
         public void onAllPrintJobsForServiceHandled(ComponentName printService);
-        public void onPrintJobStateChanged(PrintJobId printJobId, int appId);
+        public void onPrintJobStateChanged(PrintJobInfo printJob);
     }
 
     public RemotePrintSpooler(Context context, int userId,
@@ -280,30 +279,6 @@
         }
     }
 
-    public final void forgetPrintJobs(List<PrintJobId> printJobIds) {
-        throwIfCalledOnMainThread();
-        synchronized (mLock) {
-            throwIfDestroyedLocked();
-            mCanUnbind = false;
-        }
-        try {
-            getRemoteInstanceLazy().forgetPrintJobs(printJobIds);
-        } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error forgeting print jobs", re);
-        } catch (TimeoutException te) {
-            Slog.e(LOG_TAG, "Error forgeting print jobs", te);
-        } finally {
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
-                        + "] forgetPrintJobs()");
-            }
-            synchronized (mLock) {
-                mCanUnbind = true;
-                mLock.notifyAll();
-            }
-        }
-    }
-
     public final void destroy() {
         throwIfCalledOnMainThread();
         if (DEBUG) {
@@ -323,18 +298,15 @@
                     .append(String.valueOf(mDestroyed)).println();
             pw.append(prefix).append("bound=")
                     .append((mRemoteInstance != null) ? "true" : "false").println();
-            pw.append(prefix).append("print jobs:").println();
-            if (mRemoteInstance != null) {
-                List<PrintJobInfo> printJobs = getPrintJobInfos(null,
-                        PrintJobInfo.STATE_ANY, PrintManager.APP_ID_ANY);
-                if (printJobs != null) {
-                    final int printJobCount = printJobs.size();
-                    for (int i = 0; i < printJobCount; i++) {
-                        PrintJobInfo printJob = printJobs.get(i);
-                        pw.append(prefix).append(prefix).append(printJob.toString());
-                        pw.println();
-                    }
-                }
+
+            pw.flush();
+
+            try {
+                getRemoteInstanceLazy().asBinder().dump(fd, new String[]{prefix});
+            } catch (TimeoutException te) {
+                /* ignore */
+            } catch (RemoteException re) {
+                /* ignore */
             }
         }
     }
@@ -346,8 +318,8 @@
         }
     }
 
-    private void onPrintJobStateChanged(PrintJobId printJobId, int appId) {
-        mCallbacks.onPrintJobStateChanged(printJobId, appId);
+    private void onPrintJobStateChanged(PrintJobInfo printJob) {
+        mCallbacks.onPrintJobStateChanged(printJob);
     }
 
     private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
@@ -625,12 +597,12 @@
         }
 
         @Override
-        public void onPrintJobStateChanged(PrintJobId printJobId, int appId) {
+        public void onPrintJobStateChanged(PrintJobInfo printJob) {
             RemotePrintSpooler spooler = mWeakSpooler.get();
             if (spooler != null) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    spooler.onPrintJobStateChanged(printJobId, appId);
+                    spooler.onPrintJobStateChanged(printJob);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index e5f5842..bc70fe3 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -33,7 +33,6 @@
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.os.UserManager;
 import android.print.IPrintClient;
 import android.print.IPrintDocumentAdapter;
 import android.print.IPrintJobStateChangeListener;
@@ -52,6 +51,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.R;
 import com.android.internal.os.BackgroundThread;
@@ -62,6 +62,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -93,8 +94,8 @@
     private final Set<ComponentName> mEnabledServices =
             new ArraySet<ComponentName>();
 
-    private final CreatedPrintJobTracker mCreatedPrintJobTracker =
-            new CreatedPrintJobTracker();
+    private final PrintJobForAppCache mPrintJobForAppCache =
+            new PrintJobForAppCache();
 
     private final Object mLock;
 
@@ -155,23 +156,22 @@
     public PrintJobInfo print(String printJobName, final IPrintClient client,
             final IPrintDocumentAdapter documentAdapter, PrintAttributes attributes,
             int appId) {
-        PrintJobId printJobId = new PrintJobId();
-
-        // Track this job so we can forget it when the creator dies.
-        if (!mCreatedPrintJobTracker.onPrintJobCreatedLocked(client.asBinder(), printJobId)) {
-            // Not adding a print job means the client is dead - done.
-            return null;
-        }
-
         // Create print job place holder.
         final PrintJobInfo printJob = new PrintJobInfo();
-        printJob.setId(printJobId);
+        printJob.setId(new PrintJobId());
         printJob.setAppId(appId);
         printJob.setLabel(printJobName);
         printJob.setAttributes(attributes);
         printJob.setState(PrintJobInfo.STATE_CREATED);
         printJob.setCopies(1);
 
+        // Track this job so we can forget it when the creator dies.
+        if (!mPrintJobForAppCache.onPrintJobCreated(client.asBinder(), appId,
+                printJob)) {
+            // Not adding a print job means the client is dead - done.
+            return null;
+        }
+
         // Spin the spooler to add the job and show the config UI.
         new AsyncTask<Void, Void, Void>() {
             @Override
@@ -185,10 +185,42 @@
     }
 
     public List<PrintJobInfo> getPrintJobInfos(int appId) {
-        return mSpooler.getPrintJobInfos(null, PrintJobInfo.STATE_ANY, appId);
+        List<PrintJobInfo> cachedPrintJobs = mPrintJobForAppCache.getPrintJobs(appId);
+        // Note that the print spooler is not storing print jobs that
+        // are in a terminal state as it is non-trivial to properly update
+        // the spooler state for when to forget print jobs in terminal state.
+        // Therefore, we fuse the cached print jobs for running apps (some
+        // jobs are in a terminal state) with the ones that the print
+        // spooler knows about (some jobs are being processed).
+        ArrayMap<PrintJobId, PrintJobInfo> result =
+                new ArrayMap<PrintJobId, PrintJobInfo>();
+
+        // Add the cached print jobs for running apps.
+        final int cachedPrintJobCount = cachedPrintJobs.size();
+        for (int i = 0; i < cachedPrintJobCount; i++) {
+            PrintJobInfo cachedPrintJob = cachedPrintJobs.get(i);
+            result.put(cachedPrintJob.getId(), cachedPrintJob);
+        }
+
+        // Add everything else the spooler knows about.
+        List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(null,
+                PrintJobInfo.STATE_ANY, appId);
+        if (printJobs != null) {
+            final int printJobCount = printJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo printJob = printJobs.get(i);
+                result.put(printJob.getId(), printJob);
+            }
+        }
+
+        return new ArrayList<PrintJobInfo>(result.values());
     }
 
     public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
+        PrintJobInfo printJob = mPrintJobForAppCache.getPrintJob(printJobId, appId);
+        if (printJob != null) {
+            return printJob;
+        }
         return mSpooler.getPrintJobInfo(printJobId, appId);
     }
 
@@ -242,6 +274,12 @@
         }
     }
 
+    public List<PrintServiceInfo> getInstalledPrintServices() {
+        synchronized (mLock) {
+            return mInstalledServices;
+        }
+    }
+
     public void createPrinterDiscoverySession(IPrinterDiscoveryObserver observer) {
         synchronized (mLock) {
             throwIfDestroyedLocked();
@@ -398,9 +436,10 @@
     }
 
     @Override
-    public void onPrintJobStateChanged(PrintJobId printJobId, int appId) {
+    public void onPrintJobStateChanged(PrintJobInfo printJob) {
+        mPrintJobForAppCache.onPrintJobStateChanged(printJob);
         mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_JOB_STATE_CHANGED,
-                appId, 0, printJobId).sendToTarget();
+                printJob.getAppId(), 0, printJob.getId()).sendToTarget();
     }
 
     @Override
@@ -525,6 +564,9 @@
             pw.println();
         }
 
+        pw.append(prefix).append(tab).append("cached print jobs:").println();
+        mPrintJobForAppCache.dump(pw, prefix + tab + tab);
+
         pw.append(prefix).append(tab).append("discovery mediator:").println();
         if (mPrinterDiscoverySession != null) {
             mPrinterDiscoverySession.dump(pw, prefix + tab + tab);
@@ -1424,34 +1466,19 @@
         }
     }
 
-    private final class CreatedPrintJobTracker {
-        private final ArrayMap<IBinder, List<PrintJobId>> mCreatedPrintJobs =
-                new ArrayMap<IBinder, List<PrintJobId>>();
+    private final class PrintJobForAppCache {
+        private final SparseArray<List<PrintJobInfo>> mPrintJobsForRunningApp =
+                new SparseArray<List<PrintJobInfo>>();
 
-        public boolean onPrintJobCreatedLocked(final IBinder creator, PrintJobId printJobId) {
+        public boolean onPrintJobCreated(final IBinder creator, final int appId,
+                PrintJobInfo printJob) {
             try {
                 creator.linkToDeath(new DeathRecipient() {
                     @Override
                     public void binderDied() {
                         creator.unlinkToDeath(this, 0);
-                        UserManager userManager = (UserManager) mContext.getSystemService(
-                                Context.USER_SERVICE);
-                        // If the death is a result of the user being removed, then
-                        // do nothing since the spooler data for this user will be
-                        // wiped and we cannot bind to the spooler at this point.
-                        if (userManager.getUserInfo(mUserId) == null) {
-                            return;
-                        }
-                        List<PrintJobId> printJobIds = null;
                         synchronized (mLock) {
-                            printJobIds = mCreatedPrintJobs.remove(creator);
-                            if (printJobIds == null) {
-                                return;
-                            }
-                            printJobIds = new ArrayList<PrintJobId>(printJobIds);
-                        }
-                        if (printJobIds != null) {
-                            mSpooler.forgetPrintJobs(printJobIds);
+                            mPrintJobsForRunningApp.remove(appId);
                         }
                     }
                 }, 0);
@@ -1460,14 +1487,93 @@
                 return false;
             }
             synchronized (mLock) {
-                List<PrintJobId> printJobIds = mCreatedPrintJobs.get(creator);
-                if (printJobIds == null) {
-                    printJobIds = new ArrayList<PrintJobId>();
-                    mCreatedPrintJobs.put(creator, printJobIds);
+                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
+                if (printJobsForApp == null) {
+                    printJobsForApp = new ArrayList<PrintJobInfo>();
+                    mPrintJobsForRunningApp.put(appId, printJobsForApp);
                 }
-                printJobIds.add(printJobId);
+                printJobsForApp.add(printJob);
             }
             return true;
         }
+
+        public void onPrintJobStateChanged(PrintJobInfo printJob) {
+            synchronized (mLock) {
+                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(
+                        printJob.getAppId());
+                if (printJobsForApp == null) {
+                    return;
+                }
+                final int printJobCount = printJobsForApp.size();
+                for (int i = 0; i < printJobCount; i++) {
+                    PrintJobInfo oldPrintJob = printJobsForApp.get(i);
+                    if (oldPrintJob.getId().equals(printJob.getId())) {
+                        printJobsForApp.set(i, printJob);
+                    }
+                }
+            }
+        }
+
+        public PrintJobInfo getPrintJob(PrintJobId printJobId, int appId) {
+            synchronized (mLock) {
+                List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
+                if (printJobsForApp == null) {
+                    return null;
+                }
+                final int printJobCount = printJobsForApp.size();
+                for (int i = 0; i < printJobCount; i++) {
+                    PrintJobInfo printJob = printJobsForApp.get(i);
+                    if (printJob.getId().equals(printJobId)) {
+                        return printJob;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public List<PrintJobInfo> getPrintJobs(int appId) {
+            synchronized (mLock) {
+                List<PrintJobInfo> printJobs = null;
+                if (appId == PrintManager.APP_ID_ANY) {
+                    final int bucketCount = mPrintJobsForRunningApp.size();
+                    for (int i = 0; i < bucketCount; i++) {
+                        List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
+                        if (printJobs == null) {
+                            printJobs = new ArrayList<PrintJobInfo>();
+                        }
+                        printJobs.addAll(bucket);
+                    }
+                } else {
+                    List<PrintJobInfo> bucket = mPrintJobsForRunningApp.get(appId);
+                    if (bucket != null) {
+                        if (printJobs == null) {
+                            printJobs = new ArrayList<PrintJobInfo>();
+                        }
+                        printJobs.addAll(bucket);
+                    }
+                }
+                if (printJobs != null) {
+                    return printJobs;
+                }
+                return Collections.emptyList();
+            }
+        }
+
+        public void dump(PrintWriter pw, String prefix) {
+            synchronized (mLock) {
+                String tab = "  ";
+                final int bucketCount = mPrintJobsForRunningApp.size();
+                for (int i = 0; i < bucketCount; i++) {
+                    final int appId = mPrintJobsForRunningApp.keyAt(i);
+                    pw.append(prefix).append("appId=" + appId).append(':').println();
+                    List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
+                    final int printJobCount = bucket.size();
+                    for (int j = 0; j < printJobCount; j++) {
+                        PrintJobInfo printJob = bucket.get(j);
+                        pw.append(prefix).append(tab).append(printJob.toString()).println();
+                    }
+                }
+            }
+        }
     }
 }
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index beeb899..2798104 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -162,6 +162,7 @@
     void moveStack(TaskStack stack, boolean toTop) {
         mStackHistory.remove(stack);
         mStackHistory.add(toTop ? mStackHistory.size() : 0, stack);
+        mService.moveStackWindowsLocked(stack);
     }
 
     public boolean isPrivate() {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index e330f8b..80c50cc 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -4715,7 +4715,7 @@
         }
     }
 
-    private void moveStackWindowsLocked(TaskStack stack) {
+    void moveStackWindowsLocked(TaskStack stack) {
         DisplayContent displayContent = stack.getDisplayContent();
 
         // First remove all of the windows from the list.
@@ -4782,7 +4782,6 @@
                 }
                 stack.moveTaskToTop(task);
                 displayContent.moveStack(stack, true);
-                moveStackWindowsLocked(stack);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -10142,16 +10141,6 @@
         return mSafeMode;
     }
 
-    @Override
-    public void showAssistant() {
-        // TODO: What permission?
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
-                != PackageManager.PERMISSION_GRANTED) {
-            return;
-        }
-        mPolicy.showAssistant();
-    }
-
     void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
         pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
         mPolicy.dump("    ", pw, args);
diff --git a/tests/TransitionTests/res/layout/crossfade_multiple.xml b/tests/TransitionTests/res/layout/crossfade_multiple.xml
index ca32ecb..3e6d551 100644
--- a/tests/TransitionTests/res/layout/crossfade_multiple.xml
+++ b/tests/TransitionTests/res/layout/crossfade_multiple.xml
@@ -33,6 +33,11 @@
                      android:onClick="changeTransitionType"
                      android:id="@+id/textfade2"
                      android:text="@string/textfade2"/>
+        <RadioButton android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:onClick="changeTransitionType"
+                     android:id="@+id/textfade3"
+                     android:text="@string/textfade3"/>
     </RadioGroup>
     <LinearLayout android:orientation="horizontal"
                   android:layout_width="match_parent"
diff --git a/tests/TransitionTests/res/values/strings.xml b/tests/TransitionTests/res/values/strings.xml
index 9cf7a94..e3cff48 100644
--- a/tests/TransitionTests/res/values/strings.xml
+++ b/tests/TransitionTests/res/values/strings.xml
@@ -55,6 +55,7 @@
     <string name="reveal">Reveal</string>
     <string name="crossfade">Crossfade</string>
     <string name="inout">In/Out</string>
-    <string name="textfade1">T1</string>
-    <string name="textfade2">T2</string>
+    <string name="textfade1">CT</string>
+    <string name="textfade2">CTO</string>
+    <string name="textfade3">CTI</string>
 </resources>
diff --git a/tests/TransitionTests/src/com/android/transitiontests/ChangingText.java b/tests/TransitionTests/src/com/android/transitiontests/ChangingText.java
index a1ddd74..01d46b2 100644
--- a/tests/TransitionTests/src/com/android/transitiontests/ChangingText.java
+++ b/tests/TransitionTests/src/com/android/transitiontests/ChangingText.java
@@ -22,7 +22,7 @@
 import android.transition.Scene;
 import android.transition.TransitionSet;
 import android.transition.ChangeBounds;
-import android.transition.TextChange;
+import android.transition.ChangeText;
 import android.transition.TransitionManager;
 
 public class ChangingText extends Activity {
@@ -44,7 +44,7 @@
         mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.changing_text_2, this);
 
         mChanger = new TransitionSet().setOrdering(TransitionSet.ORDERING_TOGETHER);
-        mChanger.addTransition(new ChangeBounds()).addTransition(new TextChange());
+        mChanger.addTransition(new ChangeBounds()).addTransition(new ChangeText());
 
         mCurrentScene = mScene1;
     }
diff --git a/tests/TransitionTests/src/com/android/transitiontests/ClippingText.java b/tests/TransitionTests/src/com/android/transitiontests/ClippingText.java
index 85702fa..54c44e2 100644
--- a/tests/TransitionTests/src/com/android/transitiontests/ClippingText.java
+++ b/tests/TransitionTests/src/com/android/transitiontests/ClippingText.java
@@ -23,7 +23,7 @@
 import android.transition.Scene;
 import android.widget.Button;
 import android.transition.Fade;
-import android.transition.TextChange;
+import android.transition.ChangeText;
 import android.transition.TransitionSet;
 import android.transition.TransitionManager;
 
@@ -51,7 +51,7 @@
         mChanger = new TransitionSet().setOrdering(TransitionSet.ORDERING_TOGETHER);
         ChangeBounds changeBounds = new ChangeBounds();
         changeBounds.setResizeClip(true);
-        mChanger.addTransition(changeBounds).addTransition(new TextChange());
+        mChanger.addTransition(changeBounds).addTransition(new ChangeText());
 
         mCurrentScene = mScene1;
     }
diff --git a/tests/TransitionTests/src/com/android/transitiontests/CrossfadeMultiple.java b/tests/TransitionTests/src/com/android/transitiontests/CrossfadeMultiple.java
index d784f75..469ee8b 100644
--- a/tests/TransitionTests/src/com/android/transitiontests/CrossfadeMultiple.java
+++ b/tests/TransitionTests/src/com/android/transitiontests/CrossfadeMultiple.java
@@ -21,7 +21,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.transition.Crossfade;
-import android.transition.TextChange;
+import android.transition.ChangeText;
 import android.transition.Transition;
 import android.transition.TransitionSet;
 import android.transition.TransitionManager;
@@ -41,7 +41,7 @@
     Button mButton;
     Crossfade mCrossfade;
     TransitionSet mCrossfadeGroup;
-    TransitionSet mTextChangeGroup1, mTextChangeGroup2;
+    TransitionSet mTextChangeGroup1, mTextChangeGroup2, mTextChangeGroup3;
     TransitionSet mInOutGroup;
 
     @Override
@@ -74,18 +74,21 @@
         mInOutGroup.addTransition(inOut).addTransition(changeBounds);
 
         mTextChangeGroup1 = new TransitionSet();
-        TextChange textChangeInOut = new TextChange();
-        textChangeInOut.setChangeBehavior(TextChange.CHANGE_BEHAVIOR_OUT_IN);
-        mTextChangeGroup1.addTransition(textChangeInOut).addTransition(new ChangeBounds());
+        ChangeText changeTextInOut = new ChangeText();
+        changeTextInOut.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN);
+        mTextChangeGroup1.addTransition(changeTextInOut).addTransition(new ChangeBounds());
 
         mTextChangeGroup2 = new TransitionSet();
         mTextChangeGroup2.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
-        TextChange textChangeOut = new TextChange();
-        textChangeOut.setChangeBehavior(TextChange.CHANGE_BEHAVIOR_OUT);
-        TextChange textChangeIn = new TextChange();
-        textChangeIn.setChangeBehavior(TextChange.CHANGE_BEHAVIOR_IN);
-        mTextChangeGroup2.addTransition(textChangeOut).addTransition(new ChangeBounds()).
-                addTransition(textChangeIn);
+        ChangeText changeTextOut = new ChangeText();
+        changeTextOut.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT);
+        mTextChangeGroup2.addTransition(changeTextOut).addTransition(new ChangeBounds());
+
+        mTextChangeGroup3 = new TransitionSet();
+        mTextChangeGroup3.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
+        ChangeText changeTextIn = new ChangeText();
+        changeTextIn.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_IN);
+        mTextChangeGroup3.addTransition(changeTextIn).addTransition(new ChangeBounds());
     }
 
     public void sendMessage(View view) {
@@ -135,6 +138,9 @@
             case R.id.textfade2:
                 mTransition = mTextChangeGroup2;
                 break;
+            case R.id.textfade3:
+                mTransition = mTextChangeGroup3;
+                break;
         }
     }
 }
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 1e3b058..632efe0 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -346,6 +346,8 @@
     LABEL_ATTR = 0x01010001,
     ICON_ATTR = 0x01010002,
     NAME_ATTR = 0x01010003,
+    PERMISSION_ATTR = 0x01010006,
+    RESOURCE_ATTR = 0x01010025,
     DEBUGGABLE_ATTR = 0x0101000f,
     VERSION_CODE_ATTR = 0x0101021b,
     VERSION_NAME_ATTR = 0x0101021c,
@@ -372,6 +374,7 @@
     COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
     LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
     PUBLIC_KEY_ATTR = 0x010103a6,
+    CATEGORY_ATTR = 0x010103e8,
 };
 
 const char *getComponentName(String8 &pkgName, String8 &componentName) {
@@ -424,6 +427,61 @@
     printf("\n");
 }
 
+Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost,
+        String8 *outError = NULL)
+{
+    Asset* aidAsset = assets.openNonAsset(xmlPath, Asset::ACCESS_BUFFER);
+    if (aidAsset == NULL) {
+        if (outError != NULL) *outError = "xml resource does not exist";
+        return Vector<String8>();
+    }
+
+    const String8 serviceTagName(offHost ? "offhost-apdu-service" : "host-apdu-service");
+
+    bool withinApduService = false;
+    Vector<String8> categories;
+
+    String8 error;
+    ResXMLTree tree;
+    tree.setTo(aidAsset->getBuffer(true), aidAsset->getLength());
+
+    size_t len;
+    int depth = 0;
+    ResXMLTree::event_code_t code;
+    while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+        if (code == ResXMLTree::END_TAG) {
+            depth--;
+            String8 tag(tree.getElementName(&len));
+
+            if (depth == 0 && tag == serviceTagName) {
+                withinApduService = false;
+            }
+
+        } else if (code == ResXMLTree::START_TAG) {
+            depth++;
+            String8 tag(tree.getElementName(&len));
+
+            if (depth == 1) {
+                if (tag == serviceTagName) {
+                    withinApduService = true;
+                }
+            } else if (depth == 2 && withinApduService) {
+                if (tag == "aid-group") {
+                    String8 category = getAttribute(tree, CATEGORY_ATTR, &error);
+                    if (error != "") {
+                        if (outError != NULL) *outError = error;
+                        return Vector<String8>();
+                    }
+
+                    categories.add(category);
+                }
+            }
+        }
+    }
+    aidAsset->close();
+    return categories;
+}
+
 /*
  * Handle the "dump" command, to extract select data from an archive.
  */
@@ -631,12 +689,31 @@
             bool hasOtherServices = false;
             bool hasWallpaperService = false;
             bool hasImeService = false;
+            bool hasAccessibilityService = false;
+            bool hasPrintService = false;
             bool hasWidgetReceivers = false;
+            bool hasDeviceAdminReceiver = false;
             bool hasIntentFilter = false;
+            bool hasPaymentService = false;
             bool actMainActivity = false;
             bool actWidgetReceivers = false;
+            bool actDeviceAdminEnabled = false;
             bool actImeService = false;
             bool actWallpaperService = false;
+            bool actAccessibilityService = false;
+            bool actPrintService = false;
+            bool actHostApduService = false;
+            bool actOffHostApduService = false;
+            bool hasMetaHostPaymentCategory = false;
+            bool hasMetaOffHostPaymentCategory = false;
+
+            // These permissions are required by services implementing services
+            // the system binds to (IME, Accessibility, PrintServices, etc.)
+            bool hasBindDeviceAdminPermission = false;
+            bool hasBindInputMethodPermission = false;
+            bool hasBindAccessibilityServicePermission = false;
+            bool hasBindPrintServicePermission = false;
+            bool hasBindNfcServicePermission = false;
 
             // These two implement the implicit permissions that are granted
             // to pre-1.6 applications.
@@ -747,6 +824,13 @@
                             hasOtherActivities |= withinActivity;
                             hasOtherReceivers |= withinReceiver;
                             hasOtherServices |= withinService;
+                        } else {
+                            if (withinService) {
+                                hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
+                                        hasBindNfcServicePermission);
+                                hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
+                                        hasBindNfcServicePermission);
+                            }
                         }
                         withinActivity = false;
                         withinService = false;
@@ -760,11 +844,18 @@
                                 hasOtherActivities |= !actMainActivity;
                             } else if (withinReceiver) {
                                 hasWidgetReceivers |= actWidgetReceivers;
-                                hasOtherReceivers |= !actWidgetReceivers;
+                                hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
+                                        hasBindDeviceAdminPermission);
+                                hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
                             } else if (withinService) {
                                 hasImeService |= actImeService;
                                 hasWallpaperService |= actWallpaperService;
-                                hasOtherServices |= (!actImeService && !actWallpaperService);
+                                hasAccessibilityService |= (actAccessibilityService &&
+                                        hasBindAccessibilityServicePermission);
+                                hasPrintService |= (actPrintService && hasBindPrintServicePermission);
+                                hasOtherServices |= (!actImeService && !actWallpaperService &&
+                                        !actAccessibilityService && !actPrintService &&
+                                        !actHostApduService && !actOffHostApduService);
                             }
                         }
                         withinIntentFilter = false;
@@ -1109,6 +1200,13 @@
                     withinReceiver = false;
                     withinService = false;
                     hasIntentFilter = false;
+                    hasMetaHostPaymentCategory = false;
+                    hasMetaOffHostPaymentCategory = false;
+                    hasBindDeviceAdminPermission = false;
+                    hasBindInputMethodPermission = false;
+                    hasBindAccessibilityServicePermission = false;
+                    hasBindPrintServicePermission = false;
+                    hasBindNfcServicePermission = false;
                     if (withinApplication) {
                         if(tag == "activity") {
                             withinActivity = true;
@@ -1166,6 +1264,16 @@
                                         " %s\n", error.string());
                                 goto bail;
                             }
+
+                            String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
+                            if (error == "") {
+                                if (permission == "android.permission.BIND_DEVICE_ADMIN") {
+                                    hasBindDeviceAdminPermission = true;
+                                }
+                            } else {
+                                fprintf(stderr, "ERROR getting 'android:permission' attribute for"
+                                        " receiver '%s': %s\n", receiverName.string(), error.string());
+                            }
                         } else if (tag == "service") {
                             withinService = true;
                             serviceName = getAttribute(tree, NAME_ATTR, &error);
@@ -1175,6 +1283,22 @@
                                         " service: %s\n", error.string());
                                 goto bail;
                             }
+
+                            String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
+                            if (error == "") {
+                                if (permission == "android.permission.BIND_INPUT_METHOD") {
+                                    hasBindInputMethodPermission = true;
+                                } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
+                                    hasBindAccessibilityServicePermission = true;
+                                } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
+                                    hasBindPrintServicePermission = true;
+                                } else if (permission == "android.permission.BIND_NFC_SERVICE") {
+                                    hasBindNfcServicePermission = true;
+                                }
+                            } else {
+                                fprintf(stderr, "ERROR getting 'android:permission' attribute for"
+                                        " service '%s': %s\n", serviceName.string(), error.string());
+                            }
                         }
                     } else if (withinSupportsInput && tag == "input-type") {
                         String8 name = getAttribute(tree, NAME_ATTR, &error);
@@ -1186,10 +1310,60 @@
                             goto bail;
                         }
                     }
-                } else if ((depth == 4) && (tag == "intent-filter")) {
-                    hasIntentFilter = true;
-                    withinIntentFilter = true;
-                    actMainActivity = actWidgetReceivers = actImeService = actWallpaperService = false;
+                } else if (depth == 4) {
+                    if (tag == "intent-filter") {
+                        hasIntentFilter = true;
+                        withinIntentFilter = true;
+                        actMainActivity = false;
+                        actWidgetReceivers = false;
+                        actImeService = false;
+                        actWallpaperService = false;
+                        actAccessibilityService = false;
+                        actPrintService = false;
+                        actDeviceAdminEnabled = false;
+                        actHostApduService = false;
+                        actOffHostApduService = false;
+                    } else if (withinService && tag == "meta-data") {
+                        String8 name = getAttribute(tree, NAME_ATTR, &error);
+                        if (error != "") {
+                            fprintf(stderr, "ERROR getting 'android:name' attribute for"
+                                    " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+                            goto bail;
+                        }
+
+                        if (name == "android.nfc.cardemulation.host_apdu_service" ||
+                                name == "android.nfc.cardemulation.off_host_apdu_service") {
+                            bool offHost = true;
+                            if (name == "android.nfc.cardemulation.host_apdu_service") {
+                                offHost = false;
+                            }
+
+                            String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
+                            if (error != "") {
+                                fprintf(stderr, "ERROR getting 'android:resource' attribute for"
+                                        " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+                                goto bail;
+                            }
+
+                            Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
+                                    offHost, &error);
+                            if (error != "") {
+                                fprintf(stderr, "ERROR getting AID category for service '%s'\n",
+                                        serviceName.string());
+                                goto bail;
+                            }
+
+                            const size_t catLen = categories.size();
+                            for (size_t i = 0; i < catLen; i++) {
+                                bool paymentCategory = (categories[i] == "payment");
+                                if (offHost) {
+                                    hasMetaOffHostPaymentCategory |= paymentCategory;
+                                } else {
+                                    hasMetaHostPaymentCategory |= paymentCategory;
+                                }
+                            }
+                        }
+                    }
                 } else if ((depth == 5) && withinIntentFilter){
                     String8 action;
                     if (tag == "action") {
@@ -1206,12 +1380,22 @@
                         } else if (withinReceiver) {
                             if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
                                 actWidgetReceivers = true;
+                            } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
+                                actDeviceAdminEnabled = true;
                             }
                         } else if (withinService) {
                             if (action == "android.view.InputMethod") {
                                 actImeService = true;
                             } else if (action == "android.service.wallpaper.WallpaperService") {
                                 actWallpaperService = true;
+                            } else if (action == "android.accessibilityservice.AccessibilityService") {
+                                actAccessibilityService = true;
+                            } else if (action == "android.printservice.PrintService") {
+                                actPrintService = true;
+                            } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
+                                actHostApduService = true;
+                            } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
+                                actOffHostApduService = true;
                             }
                         }
                         if (action == "android.intent.action.SEARCH") {
@@ -1411,12 +1595,24 @@
             if (hasWidgetReceivers) {
                 printf("app-widget\n");
             }
+            if (hasDeviceAdminReceiver) {
+                printf("device-admin\n");
+            }
             if (hasImeService) {
                 printf("ime\n");
             }
             if (hasWallpaperService) {
                 printf("wallpaper\n");
             }
+            if (hasAccessibilityService) {
+                printf("accessibility\n");
+            }
+            if (hasPrintService) {
+                printf("print\n");
+            }
+            if (hasPaymentService) {
+                printf("payment\n");
+            }
             if (hasOtherActivities) {
                 printf("other-activities\n");
             }
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index f0c3a75..fd7a645 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -458,11 +458,6 @@
     }
 
     @Override
-    public void showAssistant() {
-
-    }
-
-    @Override
     public IBinder getFocusedWindowToken() {
         // TODO Auto-generated method stub
         return null;