Merge "Centralize handleAppDied and fix return to home." 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/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java
index c6dbc16..6432a37 100644
--- a/core/java/android/print/PrinterDiscoverySession.java
+++ b/core/java/android/print/PrinterDiscoverySession.java
@@ -192,22 +192,46 @@
         }
     }
 
-    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.
+        final int oldPrinterCount = mPrinters.size();
+        for (int i = 0; i < oldPrinterCount; i++) {
+            PrinterId oldPrinterId = mPrinters.keyAt(i);
+            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/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/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 97cb815..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
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/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/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/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index a9e9d3a..07d4d1b 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"));
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
index a0e44d7..b96ef88 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
@@ -139,6 +139,7 @@
 
     class ViewManagerHost extends FrameLayout {
         private static final int BACKGROUND_COLOR = 0x70000000;
+
         // This is a faster way to draw the background on devices without hardware acceleration
         private final Drawable mBackgroundDrawable = new Drawable() {
             @Override
@@ -159,54 +160,10 @@
                 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;
-        }
-
-        @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);
-            } else {
-                // allowed to extend into padding (scrim / camera preview)
-                child.measure(parentWidthMeasureSpec, parentHeightMeasureSpec);
-            }
-        }
-
-        @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);
-            }
         }
 
         @Override
@@ -252,7 +209,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
@@ -285,6 +242,7 @@
         }
 
         if (force || mKeyguardView == null) {
+            mKeyguardHost.removeAllViews();
             inflateKeyguardView(options);
             mKeyguardView.requestFocus();
         }
@@ -306,7 +264,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
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/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/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..9d77518a
--- /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/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index d5059b6..a7fc995 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -933,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);
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");
             }