Merge "Piping through ability for an Activity to remove its own task. (Bug 13735914)"
diff --git a/api/current.txt b/api/current.txt
index 59aac1e..ef6aed7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6928,6 +6928,7 @@
     field public static final java.lang.String ACTION_PASTE = "android.intent.action.PASTE";
     field public static final java.lang.String ACTION_PICK = "android.intent.action.PICK";
     field public static final java.lang.String ACTION_PICK_ACTIVITY = "android.intent.action.PICK_ACTIVITY";
+    field public static final java.lang.String ACTION_PICK_DIRECTORY = "android.intent.action.PICK_DIRECTORY";
     field public static final java.lang.String ACTION_POWER_CONNECTED = "android.intent.action.ACTION_POWER_CONNECTED";
     field public static final java.lang.String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED";
     field public static final java.lang.String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
@@ -7076,6 +7077,7 @@
     field public static final int FLAG_EXCLUDE_STOPPED_PACKAGES = 16; // 0x10
     field public static final int FLAG_FROM_BACKGROUND = 4; // 0x4
     field public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 64; // 0x40
+    field public static final int FLAG_GRANT_PREFIX_URI_PERMISSION = 128; // 0x80
     field public static final int FLAG_GRANT_READ_URI_PERMISSION = 1; // 0x1
     field public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 2; // 0x2
     field public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32; // 0x20
@@ -16812,11 +16814,24 @@
 
 package android.nfc.cardemulation {
 
+  public final class AidGroup implements android.os.Parcelable {
+    ctor public AidGroup(java.util.ArrayList<java.lang.String>, java.lang.String);
+    method public int describeContents();
+    method public java.util.ArrayList<java.lang.String> getAids();
+    method public java.lang.String getCategory();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final int MAX_NUM_AIDS = 256; // 0x100
+  }
+
   public final class CardEmulation {
+    method public android.nfc.cardemulation.AidGroup getAidGroupForService(android.content.ComponentName, java.lang.String);
     method public static synchronized android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
     method public int getSelectionModeForCategory(java.lang.String);
     method public boolean isDefaultServiceForAid(android.content.ComponentName, java.lang.String);
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String);
+    method public boolean registerAidGroupForService(android.content.ComponentName, android.nfc.cardemulation.AidGroup);
+    method public boolean removeAidGroupForService(android.content.ComponentName, java.lang.String);
     field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
     field public static final java.lang.String CATEGORY_OTHER = "other";
     field public static final java.lang.String CATEGORY_PAYMENT = "payment";
@@ -19255,11 +19270,12 @@
     field public static final int L = 10000; // 0x2710
   }
 
-  public final class Bundle implements java.lang.Cloneable android.os.Parcelable {
+  public final class Bundle extends android.os.CommonBundle {
     ctor public Bundle();
     ctor public Bundle(java.lang.ClassLoader);
     ctor public Bundle(int);
     ctor public Bundle(android.os.Bundle);
+    ctor public Bundle(android.os.PersistableBundle);
     method public void clear();
     method public java.lang.Object clone();
     method public boolean containsKey(java.lang.String);
@@ -19297,6 +19313,7 @@
     method public T getParcelable(java.lang.String);
     method public android.os.Parcelable[] getParcelableArray(java.lang.String);
     method public java.util.ArrayList<T> getParcelableArrayList(java.lang.String);
+    method public android.os.PersistableBundle getPersistableBundle(java.lang.String);
     method public java.io.Serializable getSerializable(java.lang.String);
     method public short getShort(java.lang.String);
     method public short getShort(java.lang.String, short);
@@ -19310,6 +19327,7 @@
     method public boolean isEmpty();
     method public java.util.Set<java.lang.String> keySet();
     method public void putAll(android.os.Bundle);
+    method public void putAll(android.os.PersistableBundle);
     method public void putBinder(java.lang.String, android.os.IBinder);
     method public void putBoolean(java.lang.String, boolean);
     method public void putBooleanArray(java.lang.String, boolean[]);
@@ -19333,6 +19351,7 @@
     method public void putParcelable(java.lang.String, android.os.Parcelable);
     method public void putParcelableArray(java.lang.String, android.os.Parcelable[]);
     method public void putParcelableArrayList(java.lang.String, java.util.ArrayList<? extends android.os.Parcelable>);
+    method public void putPersistableBundle(java.lang.String, android.os.PersistableBundle);
     method public void putSerializable(java.lang.String, java.io.Serializable);
     method public void putShort(java.lang.String, short);
     method public void putShortArray(java.lang.String, short[]);
@@ -19361,6 +19380,9 @@
     method public abstract void onCancel();
   }
 
+   abstract class CommonBundle implements java.lang.Cloneable android.os.Parcelable {
+  }
+
   public class ConditionVariable {
     ctor public ConditionVariable();
     ctor public ConditionVariable(boolean);
@@ -19787,6 +19809,8 @@
     method public final void readMap(java.util.Map, java.lang.ClassLoader);
     method public final T readParcelable(java.lang.ClassLoader);
     method public final android.os.Parcelable[] readParcelableArray(java.lang.ClassLoader);
+    method public final android.os.PersistableBundle readPersistableBundle();
+    method public final android.os.PersistableBundle readPersistableBundle(java.lang.ClassLoader);
     method public final java.io.Serializable readSerializable();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
@@ -19827,6 +19851,7 @@
     method public final void writeNoException();
     method public final void writeParcelable(android.os.Parcelable, int);
     method public final void writeParcelableArray(T[], int);
+    method public final void writePersistableBundle(android.os.PersistableBundle);
     method public final void writeSerializable(java.io.Serializable);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
@@ -19937,6 +19962,51 @@
     field public static final int PATTERN_SIMPLE_GLOB = 2; // 0x2
   }
 
+  public final class PersistableBundle extends android.os.CommonBundle {
+    ctor public PersistableBundle();
+    ctor public PersistableBundle(java.lang.ClassLoader);
+    ctor public PersistableBundle(int);
+    ctor public PersistableBundle(android.os.PersistableBundle);
+    method public void clear();
+    method public java.lang.Object clone();
+    method public boolean containsKey(java.lang.String);
+    method public int describeContents();
+    method public java.lang.Object get(java.lang.String);
+    method public java.lang.ClassLoader getClassLoader();
+    method public double getDouble(java.lang.String);
+    method public double getDouble(java.lang.String, double);
+    method public double[] getDoubleArray(java.lang.String);
+    method public int getInt(java.lang.String);
+    method public int getInt(java.lang.String, int);
+    method public int[] getIntArray(java.lang.String);
+    method public long getLong(java.lang.String);
+    method public long getLong(java.lang.String, long);
+    method public long[] getLongArray(java.lang.String);
+    method public android.os.PersistableBundle getPersistableBundle(java.lang.String);
+    method public java.lang.String getString(java.lang.String);
+    method public java.lang.String getString(java.lang.String, java.lang.String);
+    method public java.lang.String[] getStringArray(java.lang.String);
+    method public boolean isEmpty();
+    method public java.util.Set<java.lang.String> keySet();
+    method public void putAll(android.os.PersistableBundle);
+    method public void putDouble(java.lang.String, double);
+    method public void putDoubleArray(java.lang.String, double[]);
+    method public void putInt(java.lang.String, int);
+    method public void putIntArray(java.lang.String, int[]);
+    method public void putLong(java.lang.String, long);
+    method public void putLongArray(java.lang.String, long[]);
+    method public void putPersistableBundle(java.lang.String, android.os.PersistableBundle);
+    method public void putString(java.lang.String, java.lang.String);
+    method public void putStringArray(java.lang.String, java.lang.String[]);
+    method public void readFromParcel(android.os.Parcel);
+    method public void remove(java.lang.String);
+    method public void setClassLoader(java.lang.ClassLoader);
+    method public int size();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final android.os.PersistableBundle EMPTY;
+  }
+
   public final class PowerManager {
     method public void goToSleep(long);
     method public boolean isInteractive();
@@ -22480,16 +22550,21 @@
 
   public final class DocumentsContract {
     method public static android.net.Uri buildChildDocumentsUri(java.lang.String, java.lang.String);
+    method public static android.net.Uri buildChildDocumentsViaUri(android.net.Uri, java.lang.String);
     method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
+    method public static android.net.Uri buildDocumentViaUri(android.net.Uri, java.lang.String);
     method public static android.net.Uri buildRecentDocumentsUri(java.lang.String, java.lang.String);
     method public static android.net.Uri buildRootUri(java.lang.String, java.lang.String);
     method public static android.net.Uri buildRootsUri(java.lang.String);
     method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String);
+    method public static android.net.Uri buildViaUri(java.lang.String, java.lang.String);
+    method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String);
     method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri);
     method public static java.lang.String getDocumentId(android.net.Uri);
     method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal);
     method public static java.lang.String getRootId(android.net.Uri);
     method public static java.lang.String getSearchDocumentsQuery(android.net.Uri);
+    method public static java.lang.String getViaDocumentId(android.net.Uri);
     method public static boolean isDocumentUri(android.content.Context, android.net.Uri);
     field public static final java.lang.String EXTRA_ERROR = "error";
     field public static final java.lang.String EXTRA_INFO = "info";
@@ -22526,6 +22601,7 @@
     field public static final java.lang.String COLUMN_TITLE = "title";
     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_DIR_SELECTION = 16; // 0x10
     field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
     field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
   }
@@ -22538,6 +22614,9 @@
     method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
     method public final java.lang.String getType(android.net.Uri);
     method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+    method public boolean isChildDocument(java.lang.String, java.lang.String);
+    method public final android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
+    method public final android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public abstract android.os.ParcelFileDescriptor openDocument(java.lang.String, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public android.content.res.AssetFileDescriptor openDocumentThumbnail(java.lang.String, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -22550,6 +22629,7 @@
     method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
     method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException;
     method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
+    method public final void revokeDocumentPermission(java.lang.String);
     method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
   }
 
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 92cb52c..7df55a5 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -240,6 +240,7 @@
                 "        (to embed a comma into a string escape it using \"\\,\")\n" +
                 "    [-n <COMPONENT>] [-f <FLAGS>]\n" +
                 "    [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
+                "    [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]\n" +
                 "    [--debug-log-resolution] [--exclude-stopped-packages]\n" +
                 "    [--include-stopped-packages]\n" +
                 "    [--activity-brought-to-front] [--activity-clear-top]\n" +
@@ -455,6 +456,10 @@
                 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
             } else if (opt.equals("--grant-write-uri-permission")) {
                 intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+            } else if (opt.equals("--grant-persistable-uri-permission")) {
+                intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+            } else if (opt.equals("--grant-prefix-uri-permission")) {
+                intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
             } else if (opt.equals("--exclude-stopped-packages")) {
                 intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
             } else if (opt.equals("--include-stopped-packages")) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f1ce54a..fe532bf 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1865,17 +1865,26 @@
     }
 
     private String uriModeFlagToString(int uriModeFlags) {
-        switch (uriModeFlags) {
-            case Intent.FLAG_GRANT_READ_URI_PERMISSION |
-                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION:
-                return "read and write";
-            case Intent.FLAG_GRANT_READ_URI_PERMISSION:
-                return "read";
-            case Intent.FLAG_GRANT_WRITE_URI_PERMISSION:
-                return "write";
+        StringBuilder builder = new StringBuilder();
+        if ((uriModeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+            builder.append("read and ");
         }
-        throw new IllegalArgumentException(
-                "Unknown permission mode flags: " + uriModeFlags);
+        if ((uriModeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+            builder.append("write and ");
+        }
+        if ((uriModeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0) {
+            builder.append("persistable and ");
+        }
+        if ((uriModeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0) {
+            builder.append("prefix and ");
+        }
+
+        if (builder.length() > 5) {
+            builder.setLength(builder.length() - 5);
+            return builder.toString();
+        } else {
+            throw new IllegalArgumentException("Unknown permission mode flags: " + uriModeFlags);
+        }
     }
 
     private void enforceForUri(
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a6a04d1..5cf61a8 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -41,8 +41,6 @@
     @Deprecated
     public static final int DISABLE_NOTIFICATION_TICKER
             = View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
-    public static final int DISABLE_PRIVATE_NOTIFICATIONS
-            = View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
     public static final int DISABLE_SYSTEM_INFO = View.STATUS_BAR_DISABLE_SYSTEM_INFO;
     public static final int DISABLE_HOME = View.STATUS_BAR_DISABLE_HOME;
     public static final int DISABLE_RECENT = View.STATUS_BAR_DISABLE_RECENT;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index f3c803d..5b41394 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1641,7 +1641,7 @@
      *
      * @see #getPersistedUriPermissions()
      */
-    public void takePersistableUriPermission(Uri uri, int modeFlags) {
+    public void takePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) {
         try {
             ActivityManagerNative.getDefault().takePersistableUriPermission(uri, modeFlags);
         } catch (RemoteException e) {
@@ -1656,7 +1656,7 @@
      *
      * @see #getPersistedUriPermissions()
      */
-    public void releasePersistableUriPermission(Uri uri, int modeFlags) {
+    public void releasePersistableUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags) {
         try {
             ActivityManagerNative.getDefault().releasePersistableUriPermission(uri, modeFlags);
         } catch (RemoteException e) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 906484a..cbb6cf5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2791,9 +2791,13 @@
      * @param uri The Uri you would like to grant access to.
      * @param modeFlags The desired access modes.  Any combination of
      * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
-     * Intent.FLAG_GRANT_READ_URI_PERMISSION} or
+     * Intent.FLAG_GRANT_READ_URI_PERMISSION},
      * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
-     * Intent.FLAG_GRANT_WRITE_URI_PERMISSION}.
+     * Intent.FLAG_GRANT_WRITE_URI_PERMISSION},
+     * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+     * Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION}, or
+     * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION
+     * Intent.FLAG_GRANT_PREFIX_URI_PERMISSION}.
      *
      * @see #revokeUriPermission
      */
@@ -2806,7 +2810,8 @@
      * Uri will match all previously granted Uris that are the same or a
      * sub-path of the given Uri.  That is, revoking "content://foo/target" will
      * revoke both "content://foo/target" and "content://foo/target/sub", but not
-     * "content://foo".
+     * "content://foo".  It will not remove any prefix grants that exist at a
+     * higher level.
      *
      * @param uri The Uri you would like to revoke access to.
      * @param modeFlags The desired access modes.  Any combination of
@@ -2817,7 +2822,7 @@
      *
      * @see #grantUriPermission
      */
-    public abstract void revokeUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags);
+    public abstract void revokeUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
 
     /**
      * Determine whether a particular process and user ID has been granted
@@ -2841,7 +2846,7 @@
      * @see #checkCallingUriPermission
      */
     public abstract int checkUriPermission(Uri uri, int pid, int uid,
-            @Intent.GrantUriMode int modeFlags);
+            @Intent.AccessUriMode int modeFlags);
 
     /**
      * Determine whether the calling process and user ID has been
@@ -2864,7 +2869,7 @@
      *
      * @see #checkUriPermission(Uri, int, int, int)
      */
-    public abstract int checkCallingUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags);
+    public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
 
     /**
      * Determine whether the calling process of an IPC <em>or you</em> has been granted
@@ -2884,7 +2889,7 @@
      * @see #checkCallingUriPermission
      */
     public abstract int checkCallingOrSelfUriPermission(Uri uri,
-            @Intent.GrantUriMode int modeFlags);
+            @Intent.AccessUriMode int modeFlags);
 
     /**
      * Check both a Uri and normal permission.  This allows you to perform
@@ -2910,7 +2915,7 @@
      */
     public abstract int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission,
             @Nullable String writePermission, int pid, int uid,
-            @Intent.GrantUriMode int modeFlags);
+            @Intent.AccessUriMode int modeFlags);
 
     /**
      * If a particular process and user ID has not been granted
@@ -2932,7 +2937,7 @@
      * @see #checkUriPermission(Uri, int, int, int)
      */
     public abstract void enforceUriPermission(
-            Uri uri, int pid, int uid, @Intent.GrantUriMode int modeFlags, String message);
+            Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags, String message);
 
     /**
      * If the calling process and user ID has not been granted
@@ -2954,7 +2959,7 @@
      * @see #checkCallingUriPermission(Uri, int)
      */
     public abstract void enforceCallingUriPermission(
-            Uri uri, @Intent.GrantUriMode int modeFlags, String message);
+            Uri uri, @Intent.AccessUriMode int modeFlags, String message);
 
     /**
      * If the calling process of an IPC <em>or you</em> has not been
@@ -2973,7 +2978,7 @@
      * @see #checkCallingOrSelfUriPermission(Uri, int)
      */
     public abstract void enforceCallingOrSelfUriPermission(
-            Uri uri, @Intent.GrantUriMode int modeFlags, String message);
+            Uri uri, @Intent.AccessUriMode int modeFlags, String message);
 
     /**
      * Enforce both a Uri and normal permission.  This allows you to perform
@@ -2998,7 +3003,7 @@
      */
     public abstract void enforceUriPermission(
             @Nullable Uri uri, @Nullable String readPermission,
-            @Nullable String writePermission, int pid, int uid, @Intent.GrantUriMode int modeFlags,
+            @Nullable String writePermission, int pid, int uid, @Intent.AccessUriMode int modeFlags,
             @Nullable String message);
 
     /** @hide */
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 421956b..c0f04af 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -18,6 +18,7 @@
 
 import android.content.pm.ApplicationInfo;
 import android.util.ArraySet;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -864,8 +865,9 @@
         }
 
         // Migrate any clip data and flags from target.
-        int permFlags = target.getFlags()
-                & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
+        int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
+                | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                | FLAG_GRANT_PREFIX_URI_PERMISSION);
         if (permFlags != 0) {
             ClipData targetClipData = target.getClipData();
             if (targetClipData == null && target.getData() != null) {
@@ -2698,9 +2700,11 @@
      * take the persistable permissions using
      * {@link ContentResolver#takePersistableUriPermission(Uri, int)}.
      * <p>
-     * Callers can restrict document selection to a specific kind of data, such
-     * as photos, by setting one or more MIME types in
-     * {@link #EXTRA_MIME_TYPES}.
+     * Callers must indicate the acceptable document MIME types through
+     * {@link #setType(String)}. For example, to select photos, use
+     * {@code image/*}. If multiple disjoint MIME types are acceptable, define
+     * them in {@link #EXTRA_MIME_TYPES} and {@link #setType(String)} to
+     * {@literal *}/*.
      * <p>
      * If the caller can handle multiple returned items (the user performing
      * multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE}
@@ -2710,9 +2714,10 @@
      * returned URIs can be opened with
      * {@link ContentResolver#openFileDescriptor(Uri, String)}.
      * <p>
-     * Output: The URI of the item that was picked. This must be a
-     * {@code content://} URI so that any receiver can access it. If multiple
-     * documents were selected, they are returned in {@link #getClipData()}.
+     * Output: The URI of the item that was picked, returned in
+     * {@link #getData()}. This must be a {@code content://} URI so that any
+     * receiver can access it. If multiple documents were selected, they are
+     * returned in {@link #getClipData()}.
      *
      * @see DocumentsContract
      * @see #ACTION_CREATE_DOCUMENT
@@ -2754,6 +2759,24 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
 
+    /**
+     * Activity Action: Allow the user to pick a directory. When invoked, the
+     * system will display the various {@link DocumentsProvider} instances
+     * installed on the device, letting the user navigate through them. Apps can
+     * fully manage documents within the returned directory.
+     * <p>
+     * To gain access to descendant (child, grandchild, etc) documents, use
+     * {@link DocumentsContract#buildDocumentViaUri(Uri, String)} and
+     * {@link DocumentsContract#buildChildDocumentsViaUri(Uri, String)} using
+     * the returned directory URI.
+     * <p>
+     * Output: The URI representing the selected directory.
+     *
+     * @see DocumentsContract
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PICK_DIRECTORY = "android.intent.action.PICK_DIRECTORY";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Standard intent categories (see addCategory()).
@@ -3332,6 +3355,7 @@
      * @see #ACTION_GET_CONTENT
      * @see #ACTION_OPEN_DOCUMENT
      * @see #ACTION_CREATE_DOCUMENT
+     * @see #ACTION_PICK_DIRECTORY
      */
     public static final String EXTRA_LOCAL_ONLY =
             "android.intent.extra.LOCAL_ONLY";
@@ -3425,11 +3449,29 @@
     // Intent flags (see mFlags variable).
 
     /** @hide */
-    @IntDef(flag = true,
-            value = {FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION})
+    @IntDef(flag = true, value = {
+            FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION,
+            FLAG_GRANT_PERSISTABLE_URI_PERMISSION, FLAG_GRANT_PREFIX_URI_PERMISSION })
     @Retention(RetentionPolicy.SOURCE)
     public @interface GrantUriMode {}
 
+    /** @hide */
+    @IntDef(flag = true, value = {
+            FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AccessUriMode {}
+
+    /**
+     * Test if given mode flags specify an access mode, which must be at least
+     * read and/or write.
+     *
+     * @hide
+     */
+    public static boolean isAccessUriMode(int modeFlags) {
+        return (modeFlags & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) != 0;
+    }
+
     /**
      * If set, the recipient of this Intent will be granted permission to
      * perform read operations on the URI in the Intent's data and any URIs
@@ -3491,6 +3533,17 @@
     public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 0x00000040;
 
     /**
+     * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or
+     * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant
+     * applies to any URI that is a prefix match against the original granted
+     * URI. (Without this flag, the URI must match exactly for access to be
+     * granted.) Another URI is considered a prefix match only when scheme,
+     * authority, and all path segments defined by the prefix are an exact
+     * match.
+     */
+    public static final int FLAG_GRANT_PREFIX_URI_PERMISSION = 0x00000080;
+
+    /**
      * If set, the new activity is not kept in the history stack.  As soon as
      * the user navigates away from it, the activity is finished.  This may also
      * be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
@@ -3810,9 +3863,9 @@
     /**
      * @hide Flags that can't be changed with PendingIntent.
      */
-    public static final int IMMUTABLE_FLAGS =
-            FLAG_GRANT_READ_URI_PERMISSION
-            | FLAG_GRANT_WRITE_URI_PERMISSION;
+    public static final int IMMUTABLE_FLAGS = FLAG_GRANT_READ_URI_PERMISSION
+            | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+            | FLAG_GRANT_PREFIX_URI_PERMISSION;
 
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
@@ -6350,6 +6403,8 @@
      *
      * @see #FLAG_GRANT_READ_URI_PERMISSION
      * @see #FLAG_GRANT_WRITE_URI_PERMISSION
+     * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+     * @see #FLAG_GRANT_PREFIX_URI_PERMISSION
      * @see #FLAG_DEBUG_LOG_RESOLUTION
      * @see #FLAG_FROM_BACKGROUND
      * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT
@@ -7381,9 +7436,10 @@
                     // Since we migrated in child, we need to promote ClipData
                     // and flags to ourselves to grant.
                     setClipData(target.getClipData());
-                    addFlags(target.getFlags()
-                            & (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION
-                                    | FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+                    addFlags(target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
+                            | FLAG_GRANT_WRITE_URI_PERMISSION
+                            | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                            | FLAG_GRANT_PREFIX_URI_PERMISSION));
                     return true;
                 } else {
                     return false;
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index a7a8a0a..ce70455 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -21,6 +21,7 @@
 import android.os.Parcelable;
 import android.os.StrictMode;
 import android.util.Log;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
@@ -32,8 +33,10 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.RandomAccess;
 import java.util.Set;
+
 import libcore.net.UriCodec;
 
 /**
@@ -2338,4 +2341,29 @@
             StrictMode.onFileUriExposed(location);
         }
     }
+
+    /**
+     * Test if this is a path prefix match against the given Uri. Verifies that
+     * scheme, authority, and atomic path segments match.
+     *
+     * @hide
+     */
+    public boolean isPathPrefixMatch(Uri prefix) {
+        if (!Objects.equals(getScheme(), prefix.getScheme())) return false;
+        if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false;
+
+        List<String> seg = getPathSegments();
+        List<String> prefixSeg = prefix.getPathSegments();
+
+        final int prefixSize = prefixSeg.size();
+        if (seg.size() < prefixSize) return false;
+
+        for (int i = 0; i < prefixSize; i++) {
+            if (!Objects.equals(seg.get(i), prefixSeg.get(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
 }
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index b8a5ba7..ae9796b 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -17,6 +17,7 @@
 package android.nfc;
 
 import android.content.ComponentName;
+import android.nfc.cardemulation.AidGroup;
 import android.nfc.cardemulation.ApduServiceInfo;
 import android.os.RemoteCallback;
 
@@ -29,5 +30,8 @@
     boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
     boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
     boolean setDefaultForNextTap(int userHandle, in ComponentName service);
+    boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+    AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
+    boolean removeAidGroupForService(int userHandle, in ComponentName service, String category);
     List<ApduServiceInfo> getServices(int userHandle, in String category);
 }
diff --git a/core/java/android/nfc/cardemulation/AidGroup.aidl b/core/java/android/nfc/cardemulation/AidGroup.aidl
new file mode 100644
index 0000000..56d6fa5
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/AidGroup.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.nfc.cardemulation;
+
+parcelable AidGroup;
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
new file mode 100644
index 0000000..2820f40
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -0,0 +1,165 @@
+package android.nfc.cardemulation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * The AidGroup class represents a group of ISO/IEC 7816-4
+ * Application Identifiers (AIDs) for a specific application
+ * category, along with a description resource describing
+ * the group.
+ */
+public final class AidGroup implements Parcelable {
+    /**
+     * The maximum number of AIDs that can be present in any one group.
+     */
+    public static final int MAX_NUM_AIDS = 256;
+
+    static final String TAG = "AidGroup";
+
+    final ArrayList<String> aids;
+    final String category;
+    final String description;
+
+    /**
+     * Creates a new AidGroup object.
+     *
+     * @param aids The list of AIDs present in the group
+     * @param category The category of this group
+     */
+    public AidGroup(ArrayList<String> aids, String category) {
+        if (aids == null || aids.size() == 0) {
+            throw new IllegalArgumentException("No AIDS in AID group.");
+        }
+        if (aids.size() > MAX_NUM_AIDS) {
+            throw new IllegalArgumentException("Too many AIDs in AID group.");
+        }
+        if (!isValidCategory(category)) {
+            throw new IllegalArgumentException("Category specified is not valid.");
+        }
+        this.aids = aids;
+        this.category = category;
+        this.description = null;
+    }
+
+    AidGroup(String category, String description) {
+        this.aids = new ArrayList<String>();
+        this.category = category;
+        this.description = description;
+    }
+
+    /**
+     * @return the category of this AID group
+     */
+    public String getCategory() {
+        return category;
+    }
+
+    /**
+     * @return the list of  AIDs in this group
+     */
+    public ArrayList<String> getAids() {
+        return aids;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder("Category: " + category +
+                  ", AIDs:");
+        for (String aid : aids) {
+            out.append(aid);
+            out.append(", ");
+        }
+        return out.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(category);
+        dest.writeInt(aids.size());
+        if (aids.size() > 0) {
+            dest.writeStringList(aids);
+        }
+    }
+
+    public static final Parcelable.Creator<AidGroup> CREATOR =
+            new Parcelable.Creator<AidGroup>() {
+
+        @Override
+        public AidGroup createFromParcel(Parcel source) {
+            String category = source.readString();
+            int listSize = source.readInt();
+            ArrayList<String> aidList = new ArrayList<String>();
+            if (listSize > 0) {
+                source.readStringList(aidList);
+            }
+            return new AidGroup(aidList, category);
+        }
+
+        @Override
+        public AidGroup[] newArray(int size) {
+            return new AidGroup[size];
+        }
+    };
+
+    /**
+     * @hide
+     * Note: description is not serialized, since it's not localized
+     * and resource identifiers don't make sense to persist.
+     */
+    static public AidGroup createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+        String category = parser.getAttributeValue(null, "category");
+        ArrayList<String> aids = new ArrayList<String>();
+        int eventType = parser.getEventType();
+        int minDepth = parser.getDepth();
+        while (eventType != XmlPullParser.END_DOCUMENT && parser.getDepth() >= minDepth) {
+            if (eventType == XmlPullParser.START_TAG) {
+                String tagName = parser.getName();
+                if (tagName.equals("aid")) {
+                    String aid = parser.getAttributeValue(null, "value");
+                    if (aid != null) {
+                        aids.add(aid);
+                    }
+                } else {
+                    Log.d(TAG, "Ignorning unexpected tag: " + tagName);
+                }
+            }
+            eventType = parser.next();
+        }
+        if (category != null && aids.size() > 0) {
+            return new AidGroup(aids, category);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void writeAsXml(XmlSerializer out) throws IOException {
+        out.attribute(null, "category", category);
+        for (String aid : aids) {
+            out.startTag(null, "aid");
+            out.attribute(null, "value", aid);
+            out.endTag(null, "aid");
+        }
+    }
+
+    boolean isValidCategory(String category) {
+        return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
+                CardEmulation.CATEGORY_OTHER.equals(category);
+    }
+}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index d7ef4bc..94f35ed 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -35,9 +35,12 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @hide
@@ -56,24 +59,19 @@
     final String mDescription;
 
     /**
-     * Convenience AID list
-     */
-    final ArrayList<String> mAids;
-
-    /**
      * Whether this service represents AIDs running on the host CPU
      */
     final boolean mOnHost;
 
     /**
-     * All AID groups this service handles
+     * Mapping from category to static AID group
      */
-    final ArrayList<AidGroup> mAidGroups;
+    final HashMap<String, AidGroup> mStaticAidGroups;
 
     /**
-     * Convenience hashmap
+     * Mapping from category to dynamic AID group
      */
-    final HashMap<String, AidGroup> mCategoryToGroup;
+    final HashMap<String, AidGroup> mDynamicAidGroups;
 
     /**
      * Whether this service should only be started when the device is unlocked.
@@ -86,26 +84,33 @@
     final int mBannerResourceId;
 
     /**
+     * The uid of the package the service belongs to
+     */
+    final int mUid;
+    /**
      * @hide
      */
     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
-            ArrayList<AidGroup> aidGroups, boolean requiresUnlock, int bannerResource) {
+            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
+            boolean requiresUnlock, int bannerResource, int uid) {
         this.mService = info;
         this.mDescription = description;
-        this.mAidGroups = aidGroups;
-        this.mAids = new ArrayList<String>();
-        this.mCategoryToGroup = new HashMap<String, AidGroup>();
+        this.mStaticAidGroups = new HashMap<String, AidGroup>();
+        this.mDynamicAidGroups = new HashMap<String, AidGroup>();
         this.mOnHost = onHost;
         this.mRequiresDeviceUnlock = requiresUnlock;
-        for (AidGroup aidGroup : aidGroups) {
-            this.mCategoryToGroup.put(aidGroup.category, aidGroup);
-            this.mAids.addAll(aidGroup.aids);
+        for (AidGroup aidGroup : staticAidGroups) {
+            this.mStaticAidGroups.put(aidGroup.category, aidGroup);
+        }
+        for (AidGroup aidGroup : dynamicAidGroups) {
+            this.mDynamicAidGroups.put(aidGroup.category, aidGroup);
         }
         this.mBannerResourceId = bannerResource;
+        this.mUid = uid;
     }
 
-    public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost)
-            throws XmlPullParserException, IOException {
+    public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) throws
+            XmlPullParserException, IOException {
         ServiceInfo si = info.serviceInfo;
         XmlResourceParser parser = null;
         try {
@@ -163,10 +168,10 @@
                 sa.recycle();
             }
 
-            mAidGroups = new ArrayList<AidGroup>();
-            mCategoryToGroup = new HashMap<String, AidGroup>();
-            mAids = new ArrayList<String>();
+            mStaticAidGroups = new HashMap<String, AidGroup>();
+            mDynamicAidGroups = new HashMap<String, AidGroup>();
             mOnHost = onHost;
+
             final int depth = parser.getDepth();
             AidGroup currentGroup = null;
 
@@ -179,14 +184,14 @@
                     final TypedArray groupAttrs = res.obtainAttributes(attrs,
                             com.android.internal.R.styleable.AidGroup);
                     // Get category of AID group
-                    String groupDescription = groupAttrs.getString(
-                            com.android.internal.R.styleable.AidGroup_description);
                     String groupCategory = groupAttrs.getString(
                             com.android.internal.R.styleable.AidGroup_category);
+                    String groupDescription = groupAttrs.getString(
+                            com.android.internal.R.styleable.AidGroup_description);
                     if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
                         groupCategory = CardEmulation.CATEGORY_OTHER;
                     }
-                    currentGroup = mCategoryToGroup.get(groupCategory);
+                    currentGroup = mStaticAidGroups.get(groupCategory);
                     if (currentGroup != null) {
                         if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
                             Log.e(TAG, "Not allowing multiple aid-groups in the " +
@@ -200,9 +205,8 @@
                 } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
                         currentGroup != null) {
                     if (currentGroup.aids.size() > 0) {
-                        if (!mCategoryToGroup.containsKey(currentGroup.category)) {
-                            mAidGroups.add(currentGroup);
-                            mCategoryToGroup.put(currentGroup.category, currentGroup);
+                        if (!mStaticAidGroups.containsKey(currentGroup.category)) {
+                            mStaticAidGroups.put(currentGroup.category, currentGroup);
                         }
                     } else {
                         Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
@@ -216,7 +220,6 @@
                             toUpperCase();
                     if (isValidAid(aid) && !currentGroup.aids.contains(aid)) {
                         currentGroup.aids.add(aid);
-                        mAids.add(aid);
                     } else {
                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
                     }
@@ -228,6 +231,8 @@
         } finally {
             if (parser != null) parser.close();
         }
+        // Set uid
+        mUid = si.applicationInfo.uid;
     }
 
     public ComponentName getComponent() {
@@ -235,16 +240,58 @@
                 mService.serviceInfo.name);
     }
 
+    /**
+     * Returns a consolidated list of AIDs from the AID groups
+     * registered by this service. Note that if a service has both
+     * a static (manifest-based) AID group for a category and a dynamic
+     * AID group, only the dynamically registered AIDs will be returned
+     * for that category.
+     * @return List of AIDs registered by the service
+     */
     public ArrayList<String> getAids() {
-        return mAids;
+        final ArrayList<String> aids = new ArrayList<String>();
+        for (AidGroup group : getAidGroups()) {
+            aids.addAll(group.aids);
+        }
+        return aids;
     }
 
+    /**
+     * Returns the registered AID group for this category.
+     */
+    public AidGroup getDynamicAidGroupForCategory(String category) {
+        return mDynamicAidGroups.get(category);
+    }
+
+    public boolean removeDynamicAidGroupForCategory(String category) {
+        return (mDynamicAidGroups.remove(category) != null);
+    }
+
+    /**
+     * Returns a consolidated list of AID groups
+     * registered by this service. Note that if a service has both
+     * a static (manifest-based) AID group for a category and a dynamic
+     * AID group, only the dynamically registered AID group will be returned
+     * for that category.
+     * @return List of AIDs registered by the service
+     */
     public ArrayList<AidGroup> getAidGroups() {
-        return mAidGroups;
+        final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
+        for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
+            groups.add(entry.getValue());
+        }
+        for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
+            if (!mDynamicAidGroups.containsKey(entry.getKey())) {
+                // Consolidate AID groups - don't return static ones
+                // if a dynamic group exists for the category.
+                groups.add(entry.getValue());
+            }
+        }
+        return groups;
     }
 
     public boolean hasCategory(String category) {
-        return mCategoryToGroup.containsKey(category);
+        return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
     }
 
     public boolean isOnHost() {
@@ -259,6 +306,14 @@
         return mDescription;
     }
 
+    public int getUid() {
+        return mUid;
+    }
+
+    public void setOrReplaceDynamicAidGroup(AidGroup aidGroup) {
+        mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
+    }
+
     public CharSequence loadLabel(PackageManager pm) {
         return mService.loadLabel(pm);
     }
@@ -304,8 +359,12 @@
         StringBuilder out = new StringBuilder("ApduService: ");
         out.append(getComponent());
         out.append(", description: " + mDescription);
-        out.append(", AID Groups: ");
-        for (AidGroup aidGroup : mAidGroups) {
+        out.append(", Static AID Groups: ");
+        for (AidGroup aidGroup : mStaticAidGroups.values()) {
+            out.append(aidGroup.toString());
+        }
+        out.append(", Dynamic AID Groups: ");
+        for (AidGroup aidGroup : mDynamicAidGroups.values()) {
             out.append(aidGroup.toString());
         }
         return out.toString();
@@ -336,12 +395,17 @@
         mService.writeToParcel(dest, flags);
         dest.writeString(mDescription);
         dest.writeInt(mOnHost ? 1 : 0);
-        dest.writeInt(mAidGroups.size());
-        if (mAidGroups.size() > 0) {
-            dest.writeTypedList(mAidGroups);
+        dest.writeInt(mStaticAidGroups.size());
+        if (mStaticAidGroups.size() > 0) {
+            dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
+        }
+        dest.writeInt(mDynamicAidGroups.size());
+        if (mDynamicAidGroups.size() > 0) {
+            dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
         }
         dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
         dest.writeInt(mBannerResourceId);
+        dest.writeInt(mUid);
     };
 
     public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
@@ -351,14 +415,21 @@
             ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
             String description = source.readString();
             boolean onHost = (source.readInt() != 0) ? true : false;
-            ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>();
-            int numGroups = source.readInt();
-            if (numGroups > 0) {
-                source.readTypedList(aidGroups, AidGroup.CREATOR);
+            ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
+            int numStaticGroups = source.readInt();
+            if (numStaticGroups > 0) {
+                source.readTypedList(staticAidGroups, AidGroup.CREATOR);
+            }
+            ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
+            int numDynamicGroups = source.readInt();
+            if (numDynamicGroups > 0) {
+                source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
             }
             boolean requiresUnlock = (source.readInt() != 0) ? true : false;
             int bannerResource = source.readInt();
-            return new ApduServiceInfo(info, onHost, description, aidGroups, requiresUnlock, bannerResource);
+            int uid = source.readInt();
+            return new ApduServiceInfo(info, onHost, description, staticAidGroups,
+                    dynamicAidGroups, requiresUnlock, bannerResource, uid);
         }
 
         @Override
@@ -367,76 +438,22 @@
         }
     };
 
-    public static class AidGroup implements Parcelable {
-        final ArrayList<String> aids;
-        final String category;
-        final String description;
-
-        AidGroup(ArrayList<String> aids, String category, String description) {
-            this.aids = aids;
-            this.category = category;
-            this.description = description;
-        }
-
-        AidGroup(String category, String description) {
-            this.aids = new ArrayList<String>();
-            this.category = category;
-            this.description = description;
-        }
-
-        public String getCategory() {
-            return category;
-        }
-
-        public ArrayList<String> getAids() {
-            return aids;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder out = new StringBuilder("Category: " + category +
-                      ", description: " + description + ", AIDs:");
-            for (String aid : aids) {
-                out.append(aid);
-                out.append(", ");
-            }
-            return out.toString();
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeString(category);
-            dest.writeString(description);
-            dest.writeInt(aids.size());
-            if (aids.size() > 0) {
-                dest.writeStringList(aids);
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("    " + getComponent() +
+                " (Description: " + getDescription() + ")");
+        pw.println("    Static AID groups:");
+        for (AidGroup group : mStaticAidGroups.values()) {
+            pw.println("        Category: " + group.category);
+            for (String aid : group.aids) {
+                pw.println("            AID: " + aid);
             }
         }
-
-        public static final Parcelable.Creator<ApduServiceInfo.AidGroup> CREATOR =
-                new Parcelable.Creator<ApduServiceInfo.AidGroup>() {
-
-            @Override
-            public AidGroup createFromParcel(Parcel source) {
-                String category = source.readString();
-                String description = source.readString();
-                int listSize = source.readInt();
-                ArrayList<String> aidList = new ArrayList<String>();
-                if (listSize > 0) {
-                    source.readStringList(aidList);
-                }
-                return new AidGroup(aidList, category, description);
+        pw.println("    Dynamic AID groups:");
+        for (AidGroup group : mDynamicAidGroups.values()) {
+            pw.println("        Category: " + group.category);
+            for (String aid : group.aids) {
+                pw.println("            AID: " + aid);
             }
-
-            @Override
-            public AidGroup[] newArray(int size) {
-                return new AidGroup[size];
-            }
-        };
+        }
     }
 }
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 58d9616..41f039c 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -168,6 +168,10 @@
         if (manager == null) {
             // Get card emu service
             INfcCardEmulation service = adapter.getCardEmulationService();
+            if (service == null) {
+                Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
+                throw new UnsupportedOperationException();
+            }
             manager = new CardEmulation(context, service);
             sCardEmus.put(context, manager);
         }
@@ -271,6 +275,109 @@
     }
 
     /**
+     * Registers a group of AIDs for the specified service.
+     *
+     * <p>If an AID group for that category was previously
+     * registered for this service (either statically
+     * through the manifest, or dynamically by using this API),
+     * that AID group will be replaced with this one.
+     *
+     * <p>Note that you can only register AIDs for a service that
+     * is running under the same UID as you are. Typically
+     * this means you need to call this from the same
+     * package as the service itself, though UIDs can also
+     * be shared between packages using shared UIDs.
+     *
+     * @param service The component name of the service
+     * @param aidGroup The group of AIDs to be registered
+     * @return whether the registration was successful.
+     */
+    public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) {
+        try {
+            return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.registerAidGroupForService(UserHandle.myUserId(), service,
+                        aidGroup);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Retrieves the currently registered AID group for the specified
+     * category for a service.
+     *
+     * <p>Note that this will only return AID groups that were dynamically
+     * registered using {@link #registerAidGroupForService(ComponentName, AidGroup)}
+     * method. It will *not* return AID groups that were statically registered
+     * in the manifest.
+     *
+     * @param service The component name of the service
+     * @param category The category of the AID group to be returned, e.g. {@link #CATEGORY_PAYMENT}
+     * @return The AID group, or null if it couldn't be found
+     */
+    public AidGroup getAidGroupForService(ComponentName service, String category) {
+        try {
+            return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+        } catch (RemoteException e) {
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return null;
+            }
+            try {
+                return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Removes a registered AID group for the specified category for the
+     * service provided.
+     *
+     * <p>Note that this will only remove AID groups that were dynamically
+     * registered using the {@link #registerAidGroupForService(ComponentName, AidGroup)}
+     * method. It will *not* remove AID groups that were statically registered in
+     * the manifest. If a dynamically registered AID group is removed using
+     * this method, and a statically registered AID group for the same category
+     * exists in the manifest, that AID group will become active again.
+     *
+     * @param service The component name of the service
+     * @param category The category of the AID group to be removed, e.g. {@link #CATEGORY_PAYMENT}
+     * @return whether the group was successfully removed.
+     */
+    public boolean removeAidGroupForService(ComponentName service, String category) {
+        try {
+            return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
      * @hide
      */
     public boolean setDefaultServiceForCategory(ComponentName service, String category) {
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index af57507..c85e418 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -17,7 +17,6 @@
 package android.os;
 
 import android.util.ArrayMap;
-import android.util.Log;
 import android.util.SparseArray;
 
 import java.io.Serializable;
@@ -29,47 +28,25 @@
  * A mapping from String values to various Parcelable types.
  *
  */
-public final class Bundle implements Parcelable, Cloneable {
-    private static final String TAG = "Bundle";
-    static final boolean DEBUG = false;
+public final class Bundle extends CommonBundle {
     public static final Bundle EMPTY;
-
-    static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
     static final Parcel EMPTY_PARCEL;
 
     static {
         EMPTY = new Bundle();
         EMPTY.mMap = ArrayMap.EMPTY;
-        EMPTY_PARCEL = Parcel.obtain();
+        EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
     }
 
-    // Invariant - exactly one of mMap / mParcelledData will be null
-    // (except inside a call to unparcel)
-
-    /* package */ ArrayMap<String, Object> mMap = null;
-
-    /*
-     * If mParcelledData is non-null, then mMap will be null and the
-     * data are stored as a Parcel containing a Bundle.  When the data
-     * are unparcelled, mParcelledData willbe set to null.
-     */
-    /* package */ Parcel mParcelledData = null;
-
     private boolean mHasFds = false;
     private boolean mFdsKnown = true;
     private boolean mAllowFds = true;
 
     /**
-     * The ClassLoader used when unparcelling data from mParcelledData.
-     */
-    private ClassLoader mClassLoader;
-
-    /**
      * Constructs a new, empty Bundle.
      */
     public Bundle() {
-        mMap = new ArrayMap<String, Object>();
-        mClassLoader = getClass().getClassLoader();
+        super();
     }
 
     /**
@@ -79,11 +56,17 @@
      * @param parcelledData a Parcel containing a Bundle
      */
     Bundle(Parcel parcelledData) {
-        readFromParcel(parcelledData);
+        super(parcelledData);
+
+        mHasFds = mParcelledData.hasFileDescriptors();
+        mFdsKnown = true;
     }
 
     /* package */ Bundle(Parcel parcelledData, int length) {
-        readFromParcelInner(parcelledData, length);
+        super(parcelledData, length);
+
+        mHasFds = mParcelledData.hasFileDescriptors();
+        mFdsKnown = true;
     }
 
     /**
@@ -94,8 +77,7 @@
      * inside of the Bundle.
      */
     public Bundle(ClassLoader loader) {
-        mMap = new ArrayMap<String, Object>();
-        mClassLoader = loader;
+        super(loader);
     }
 
     /**
@@ -105,8 +87,7 @@
      * @param capacity the initial capacity of the Bundle
      */
     public Bundle(int capacity) {
-        mMap = new ArrayMap<String, Object>(capacity);
-        mClassLoader = getClass().getClassLoader();
+        super(capacity);
     }
 
     /**
@@ -116,27 +97,20 @@
      * @param b a Bundle to be copied.
      */
     public Bundle(Bundle b) {
-        if (b.mParcelledData != null) {
-            if (b.mParcelledData == EMPTY_PARCEL) {
-                mParcelledData = EMPTY_PARCEL;
-            } else {
-                mParcelledData = Parcel.obtain();
-                mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize());
-                mParcelledData.setDataPosition(0);
-            }
-        } else {
-            mParcelledData = null;
-        }
-
-        if (b.mMap != null) {
-            mMap = new ArrayMap<String, Object>(b.mMap);
-        } else {
-            mMap = null;
-        }
+        super(b);
 
         mHasFds = b.mHasFds;
         mFdsKnown = b.mFdsKnown;
-        mClassLoader = b.mClassLoader;
+    }
+
+    /**
+     * Constructs a Bundle containing a copy of the mappings from the given
+     * PersistableBundle.
+     *
+     * @param b a Bundle to be copied.
+     */
+    public Bundle(PersistableBundle b) {
+        super(b);
     }
 
     /**
@@ -145,37 +119,17 @@
      * @hide
      */
     public static Bundle forPair(String key, String value) {
-        // TODO: optimize this case.
         Bundle b = new Bundle(1);
         b.putString(key, value);
         return b;
     }
 
     /**
-     * TODO: optimize this later (getting just the value part of a Bundle
-     * with a single pair) once Bundle.forPair() above is implemented
-     * with a special single-value Map implementation/serialization.
-     *
-     * Note: value in single-pair Bundle may be null.
-     *
      * @hide
      */
+    @Override
     public String getPairValue() {
-        unparcel();
-        int size = mMap.size();
-        if (size > 1) {
-            Log.w(TAG, "getPairValue() used on Bundle with multiple pairs.");
-        }
-        if (size == 0) {
-            return null;
-        }
-        Object o = mMap.valueAt(0);
-        try {
-            return (String) o;
-        } catch (ClassCastException e) {
-            typeWarning("getPairValue()", o, "String", e);
-            return null;
-        }
+        return super.getPairValue();
     }
 
     /**
@@ -184,15 +138,17 @@
      * @param loader An explicit ClassLoader to use when instantiating objects
      * inside of the Bundle.
      */
+    @Override
     public void setClassLoader(ClassLoader loader) {
-        mClassLoader = loader;
+        super.setClassLoader(loader);
     }
 
     /**
      * Return the ClassLoader currently associated with this Bundle.
      */
+    @Override
     public ClassLoader getClassLoader() {
-        return mClassLoader;
+        return super.getClassLoader();
     }
 
     /** @hide */
@@ -212,52 +168,11 @@
     }
 
     /**
-     * If the underlying data are stored as a Parcel, unparcel them
-     * using the currently assigned class loader.
-     */
-    /* package */ synchronized void unparcel() {
-        if (mParcelledData == null) {
-            if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
-                    + ": no parcelled data");
-            return;
-        }
-
-        if (mParcelledData == EMPTY_PARCEL) {
-            if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
-                    + ": empty");
-            if (mMap == null) {
-                mMap = new ArrayMap<String, Object>(1);
-            } else {
-                mMap.erase();
-            }
-            mParcelledData = null;
-            return;
-        }
-
-        int N = mParcelledData.readInt();
-        if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
-                + ": reading " + N + " maps");
-        if (N < 0) {
-            return;
-        }
-        if (mMap == null) {
-            mMap = new ArrayMap<String, Object>(N);
-        } else {
-            mMap.erase();
-            mMap.ensureCapacity(N);
-        }
-        mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);
-        mParcelledData.recycle();
-        mParcelledData = null;
-        if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
-                + " final map: " + mMap);
-    }
-
-    /**
      * @hide
      */
+    @Override
     public boolean isParcelled() {
-        return mParcelledData != null;
+        return super.isParcelled();
     }
 
     /**
@@ -265,25 +180,26 @@
      *
      * @return the number of mappings as an int.
      */
+    @Override
     public int size() {
-        unparcel();
-        return mMap.size();
+        return super.size();
     }
 
     /**
      * Returns true if the mapping of this Bundle is empty, false otherwise.
      */
+    @Override
     public boolean isEmpty() {
-        unparcel();
-        return mMap.isEmpty();
+        return super.isEmpty();
     }
 
     /**
      * Removes all elements from the mapping of this Bundle.
      */
+    @Override
     public void clear() {
-        unparcel();
-        mMap.clear();
+        super.clear();
+
         mHasFds = false;
         mFdsKnown = true;
     }
@@ -295,9 +211,9 @@
      * @param key a String key
      * @return true if the key is part of the mapping, false otherwise
      */
+    @Override
     public boolean containsKey(String key) {
-        unparcel();
-        return mMap.containsKey(key);
+        return super.containsKey(key);
     }
 
     /**
@@ -306,9 +222,9 @@
      * @param key a String key
      * @return an Object, or null
      */
+    @Override
     public Object get(String key) {
-        unparcel();
-        return mMap.get(key);
+        return super.get(key);
     }
 
     /**
@@ -316,24 +232,33 @@
      *
      * @param key a String key
      */
+    @Override
     public void remove(String key) {
-        unparcel();
-        mMap.remove(key);
+        super.remove(key);
     }
 
     /**
      * Inserts all mappings from the given Bundle into this Bundle.
      *
-     * @param map a Bundle
+     * @param bundle a Bundle
      */
-    public void putAll(Bundle map) {
+    public void putAll(Bundle bundle) {
         unparcel();
-        map.unparcel();
-        mMap.putAll(map.mMap);
+        bundle.unparcel();
+        mMap.putAll(bundle.mMap);
 
         // fd state is now known if and only if both bundles already knew
-        mHasFds |= map.mHasFds;
-        mFdsKnown = mFdsKnown && map.mFdsKnown;
+        mHasFds |= bundle.mHasFds;
+        mFdsKnown = mFdsKnown && bundle.mFdsKnown;
+    }
+
+    /**
+     * Inserts all mappings from the given PersistableBundle into this Bundle.
+     *
+     * @param bundle a PersistableBundle
+     */
+    public void putAll(PersistableBundle bundle) {
+        super.putAll(bundle);
     }
 
     /**
@@ -341,9 +266,9 @@
      *
      * @return a Set of String keys
      */
+    @Override
     public Set<String> keySet() {
-        unparcel();
-        return mMap.keySet();
+        return super.keySet();
     }
 
     /**
@@ -352,7 +277,7 @@
     public boolean hasFileDescriptors() {
         if (!mFdsKnown) {
             boolean fdFound = false;    // keep going until we find one or run out of data
-            
+
             if (mParcelledData != null) {
                 if (mParcelledData.hasFileDescriptors()) {
                     fdFound = true;
@@ -390,8 +315,7 @@
                         ArrayList array = (ArrayList) obj;
                         // an ArrayList here might contain either Strings or
                         // Parcelables; only look inside for Parcelables
-                        if ((array.size() > 0)
-                                && (array.get(0) instanceof Parcelable)) {
+                        if (!array.isEmpty() && (array.get(0) instanceof Parcelable)) {
                             for (int n = array.size() - 1; n >= 0; n--) {
                                 Parcelable p = (Parcelable) array.get(n);
                                 if (p != null && ((p.describeContents()
@@ -410,7 +334,7 @@
         }
         return mHasFds;
     }
-    
+
     /**
      * Inserts a Boolean value into the mapping of this Bundle, replacing
      * any existing value for the given key.  Either key or value may be null.
@@ -418,9 +342,9 @@
      * @param key a String, or null
      * @param value a Boolean, or null
      */
+    @Override
     public void putBoolean(String key, boolean value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putBoolean(key, value);
     }
 
     /**
@@ -430,9 +354,9 @@
      * @param key a String, or null
      * @param value a byte
      */
+    @Override
     public void putByte(String key, byte value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putByte(key, value);
     }
 
     /**
@@ -442,9 +366,9 @@
      * @param key a String, or null
      * @param value a char, or null
      */
+    @Override
     public void putChar(String key, char value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putChar(key, value);
     }
 
     /**
@@ -454,9 +378,9 @@
      * @param key a String, or null
      * @param value a short
      */
+    @Override
     public void putShort(String key, short value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putShort(key, value);
     }
 
     /**
@@ -466,9 +390,9 @@
      * @param key a String, or null
      * @param value an int, or null
      */
+    @Override
     public void putInt(String key, int value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putInt(key, value);
     }
 
     /**
@@ -478,9 +402,9 @@
      * @param key a String, or null
      * @param value a long
      */
+    @Override
     public void putLong(String key, long value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putLong(key, value);
     }
 
     /**
@@ -490,9 +414,9 @@
      * @param key a String, or null
      * @param value a float
      */
+    @Override
     public void putFloat(String key, float value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putFloat(key, value);
     }
 
     /**
@@ -502,9 +426,9 @@
      * @param key a String, or null
      * @param value a double
      */
+    @Override
     public void putDouble(String key, double value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putDouble(key, value);
     }
 
     /**
@@ -514,9 +438,9 @@
      * @param key a String, or null
      * @param value a String, or null
      */
+    @Override
     public void putString(String key, String value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putString(key, value);
     }
 
     /**
@@ -526,9 +450,9 @@
      * @param key a String, or null
      * @param value a CharSequence, or null
      */
+    @Override
     public void putCharSequence(String key, CharSequence value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putCharSequence(key, value);
     }
 
     /**
@@ -567,7 +491,7 @@
      * @param value an ArrayList of Parcelable objects, or null
      */
     public void putParcelableArrayList(String key,
-        ArrayList<? extends Parcelable> value) {
+            ArrayList<? extends Parcelable> value) {
         unparcel();
         mMap.put(key, value);
         mFdsKnown = false;
@@ -602,9 +526,9 @@
      * @param key a String, or null
      * @param value an ArrayList<Integer> object, or null
      */
+    @Override
     public void putIntegerArrayList(String key, ArrayList<Integer> value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putIntegerArrayList(key, value);
     }
 
     /**
@@ -614,9 +538,9 @@
      * @param key a String, or null
      * @param value an ArrayList<String> object, or null
      */
+    @Override
     public void putStringArrayList(String key, ArrayList<String> value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putStringArrayList(key, value);
     }
 
     /**
@@ -626,9 +550,9 @@
      * @param key a String, or null
      * @param value an ArrayList<CharSequence> object, or null
      */
+    @Override
     public void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putCharSequenceArrayList(key, value);
     }
 
     /**
@@ -638,9 +562,9 @@
      * @param key a String, or null
      * @param value a Serializable object, or null
      */
+    @Override
     public void putSerializable(String key, Serializable value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putSerializable(key, value);
     }
 
     /**
@@ -650,9 +574,9 @@
      * @param key a String, or null
      * @param value a boolean array object, or null
      */
+    @Override
     public void putBooleanArray(String key, boolean[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putBooleanArray(key, value);
     }
 
     /**
@@ -662,9 +586,9 @@
      * @param key a String, or null
      * @param value a byte array object, or null
      */
+    @Override
     public void putByteArray(String key, byte[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putByteArray(key, value);
     }
 
     /**
@@ -674,9 +598,9 @@
      * @param key a String, or null
      * @param value a short array object, or null
      */
+    @Override
     public void putShortArray(String key, short[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putShortArray(key, value);
     }
 
     /**
@@ -686,9 +610,9 @@
      * @param key a String, or null
      * @param value a char array object, or null
      */
+    @Override
     public void putCharArray(String key, char[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putCharArray(key, value);
     }
 
     /**
@@ -698,9 +622,9 @@
      * @param key a String, or null
      * @param value an int array object, or null
      */
+    @Override
     public void putIntArray(String key, int[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putIntArray(key, value);
     }
 
     /**
@@ -710,9 +634,9 @@
      * @param key a String, or null
      * @param value a long array object, or null
      */
+    @Override
     public void putLongArray(String key, long[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putLongArray(key, value);
     }
 
     /**
@@ -722,9 +646,9 @@
      * @param key a String, or null
      * @param value a float array object, or null
      */
+    @Override
     public void putFloatArray(String key, float[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putFloatArray(key, value);
     }
 
     /**
@@ -734,9 +658,9 @@
      * @param key a String, or null
      * @param value a double array object, or null
      */
+    @Override
     public void putDoubleArray(String key, double[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putDoubleArray(key, value);
     }
 
     /**
@@ -746,9 +670,9 @@
      * @param key a String, or null
      * @param value a String array object, or null
      */
+    @Override
     public void putStringArray(String key, String[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putStringArray(key, value);
     }
 
     /**
@@ -758,9 +682,9 @@
      * @param key a String, or null
      * @param value a CharSequence array object, or null
      */
+    @Override
     public void putCharSequenceArray(String key, CharSequence[] value) {
-        unparcel();
-        mMap.put(key, value);
+        super.putCharSequenceArray(key, value);
     }
 
     /**
@@ -776,6 +700,17 @@
     }
 
     /**
+     * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Bundle object, or null
+     */
+    public void putPersistableBundle(String key, PersistableBundle value) {
+        super.putPersistableBundle(key, value);
+    }
+
+    /**
      * Inserts an {@link IBinder} value into the mapping of this Bundle, replacing
      * any existing value for the given key.  Either key or value may be null.
      *
@@ -817,33 +752,9 @@
      * @param key a String
      * @return a boolean value
      */
+    @Override
     public boolean getBoolean(String key) {
-        unparcel();
-        if (DEBUG) Log.d(TAG, "Getting boolean in "
-                + Integer.toHexString(System.identityHashCode(this)));
-        return getBoolean(key, false);
-    }
-
-    // Log a message if the value was non-null but not of the expected type
-    private void typeWarning(String key, Object value, String className,
-        Object defaultValue, ClassCastException e) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("Key ");
-        sb.append(key);
-        sb.append(" expected ");
-        sb.append(className);
-        sb.append(" but value was a ");
-        sb.append(value.getClass().getName());
-        sb.append(".  The default value ");
-        sb.append(defaultValue);
-        sb.append(" was returned.");
-        Log.w(TAG, sb.toString());
-        Log.w(TAG, "Attempt to cast generated internal exception:", e);
-    }
-
-    private void typeWarning(String key, Object value, String className,
-        ClassCastException e) {
-        typeWarning(key, value, className, "<null>", e);
+        return super.getBoolean(key);
     }
 
     /**
@@ -854,18 +765,9 @@
      * @param defaultValue Value to return if key does not exist
      * @return a boolean value
      */
+    @Override
     public boolean getBoolean(String key, boolean defaultValue) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return defaultValue;
-        }
-        try {
-            return (Boolean) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "Boolean", defaultValue, e);
-            return defaultValue;
-        }
+        return super.getBoolean(key, defaultValue);
     }
 
     /**
@@ -875,9 +777,9 @@
      * @param key a String
      * @return a byte value
      */
+    @Override
     public byte getByte(String key) {
-        unparcel();
-        return getByte(key, (byte) 0);
+        return super.getByte(key);
     }
 
     /**
@@ -888,18 +790,9 @@
      * @param defaultValue Value to return if key does not exist
      * @return a byte value
      */
+    @Override
     public Byte getByte(String key, byte defaultValue) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return defaultValue;
-        }
-        try {
-            return (Byte) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "Byte", defaultValue, e);
-            return defaultValue;
-        }
+        return super.getByte(key, defaultValue);
     }
 
     /**
@@ -909,9 +802,9 @@
      * @param key a String
      * @return a char value
      */
+    @Override
     public char getChar(String key) {
-        unparcel();
-        return getChar(key, (char) 0);
+        return super.getChar(key);
     }
 
     /**
@@ -922,18 +815,9 @@
      * @param defaultValue Value to return if key does not exist
      * @return a char value
      */
+    @Override
     public char getChar(String key, char defaultValue) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return defaultValue;
-        }
-        try {
-            return (Character) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "Character", defaultValue, e);
-            return defaultValue;
-        }
+        return super.getChar(key, defaultValue);
     }
 
     /**
@@ -943,9 +827,9 @@
      * @param key a String
      * @return a short value
      */
+    @Override
     public short getShort(String key) {
-        unparcel();
-        return getShort(key, (short) 0);
+        return super.getShort(key);
     }
 
     /**
@@ -956,18 +840,9 @@
      * @param defaultValue Value to return if key does not exist
      * @return a short value
      */
+    @Override
     public short getShort(String key, short defaultValue) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return defaultValue;
-        }
-        try {
-            return (Short) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "Short", defaultValue, e);
-            return defaultValue;
-        }
+        return super.getShort(key, defaultValue);
     }
 
     /**
@@ -977,9 +852,9 @@
      * @param key a String
      * @return an int value
      */
+    @Override
     public int getInt(String key) {
-        unparcel();
-        return getInt(key, 0);
+        return super.getInt(key);
     }
 
     /**
@@ -990,18 +865,9 @@
      * @param defaultValue Value to return if key does not exist
      * @return an int value
      */
+    @Override
     public int getInt(String key, int defaultValue) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return defaultValue;
-        }
-        try {
-            return (Integer) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "Integer", defaultValue, e);
-            return defaultValue;
-        }
+        return super.getInt(key, defaultValue);
     }
 
     /**
@@ -1011,9 +877,9 @@
      * @param key a String
      * @return a long value
      */
+    @Override
     public long getLong(String key) {
-        unparcel();
-        return getLong(key, 0L);
+        return super.getLong(key);
     }
 
     /**
@@ -1024,18 +890,9 @@
      * @param defaultValue Value to return if key does not exist
      * @return a long value
      */
+    @Override
     public long getLong(String key, long defaultValue) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return defaultValue;
-        }
-        try {
-            return (Long) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "Long", defaultValue, e);
-            return defaultValue;
-        }
+        return super.getLong(key, defaultValue);
     }
 
     /**
@@ -1045,9 +902,9 @@
      * @param key a String
      * @return a float value
      */
+    @Override
     public float getFloat(String key) {
-        unparcel();
-        return getFloat(key, 0.0f);
+        return super.getFloat(key);
     }
 
     /**
@@ -1058,18 +915,9 @@
      * @param defaultValue Value to return if key does not exist
      * @return a float value
      */
+    @Override
     public float getFloat(String key, float defaultValue) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return defaultValue;
-        }
-        try {
-            return (Float) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "Float", defaultValue, e);
-            return defaultValue;
-        }
+        return super.getFloat(key, defaultValue);
     }
 
     /**
@@ -1079,9 +927,9 @@
      * @param key a String
      * @return a double value
      */
+    @Override
     public double getDouble(String key) {
-        unparcel();
-        return getDouble(key, 0.0);
+        return super.getDouble(key);
     }
 
     /**
@@ -1092,18 +940,9 @@
      * @param defaultValue Value to return if key does not exist
      * @return a double value
      */
+    @Override
     public double getDouble(String key, double defaultValue) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return defaultValue;
-        }
-        try {
-            return (Double) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "Double", defaultValue, e);
-            return defaultValue;
-        }
+        return super.getDouble(key, defaultValue);
     }
 
     /**
@@ -1114,15 +953,9 @@
      * @param key a String, or null
      * @return a String value, or null
      */
+    @Override
     public String getString(String key) {
-        unparcel();
-        final Object o = mMap.get(key);
-        try {
-            return (String) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "String", e);
-            return null;
-        }
+        return super.getString(key);
     }
 
     /**
@@ -1134,9 +967,9 @@
      * @return the String value associated with the given key, or defaultValue
      *     if no valid String object is currently mapped to that key.
      */
+    @Override
     public String getString(String key, String defaultValue) {
-        final String s = getString(key);
-        return (s == null) ? defaultValue : s;
+        return super.getString(key, defaultValue);
     }
 
     /**
@@ -1147,15 +980,9 @@
      * @param key a String, or null
      * @return a CharSequence value, or null
      */
+    @Override
     public CharSequence getCharSequence(String key) {
-        unparcel();
-        final Object o = mMap.get(key);
-        try {
-            return (CharSequence) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "CharSequence", e);
-            return null;
-        }
+        return super.getCharSequence(key);
     }
 
     /**
@@ -1167,9 +994,9 @@
      * @return the CharSequence value associated with the given key, or defaultValue
      *     if no valid CharSequence object is currently mapped to that key.
      */
+    @Override
     public CharSequence getCharSequence(String key, CharSequence defaultValue) {
-        final CharSequence cs = getCharSequence(key);
-        return (cs == null) ? defaultValue : cs;
+        return super.getCharSequence(key, defaultValue);
     }
 
     /**
@@ -1200,6 +1027,18 @@
      * value is explicitly associated with the key.
      *
      * @param key a String, or null
+     * @return a PersistableBundle value, or null
+     */
+    public PersistableBundle getPersistableBundle(String key) {
+        return super.getPersistableBundle(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
      * @return a Parcelable value, or null
      */
     public <T extends Parcelable> T getParcelable(String key) {
@@ -1291,18 +1130,9 @@
      * @param key a String, or null
      * @return a Serializable value, or null
      */
+    @Override
     public Serializable getSerializable(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (Serializable) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "Serializable", e);
-            return null;
-        }
+        return super.getSerializable(key);
     }
 
     /**
@@ -1313,18 +1143,9 @@
      * @param key a String, or null
      * @return an ArrayList<String> value, or null
      */
+    @Override
     public ArrayList<Integer> getIntegerArrayList(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (ArrayList<Integer>) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "ArrayList<Integer>", e);
-            return null;
-        }
+        return super.getIntegerArrayList(key);
     }
 
     /**
@@ -1335,18 +1156,9 @@
      * @param key a String, or null
      * @return an ArrayList<String> value, or null
      */
+    @Override
     public ArrayList<String> getStringArrayList(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (ArrayList<String>) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "ArrayList<String>", e);
-            return null;
-        }
+        return super.getStringArrayList(key);
     }
 
     /**
@@ -1357,18 +1169,9 @@
      * @param key a String, or null
      * @return an ArrayList<CharSequence> value, or null
      */
+    @Override
     public ArrayList<CharSequence> getCharSequenceArrayList(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (ArrayList<CharSequence>) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "ArrayList<CharSequence>", e);
-            return null;
-        }
+        return super.getCharSequenceArrayList(key);
     }
 
     /**
@@ -1379,18 +1182,9 @@
      * @param key a String, or null
      * @return a boolean[] value, or null
      */
+    @Override
     public boolean[] getBooleanArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (boolean[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "byte[]", e);
-            return null;
-        }
+        return super.getBooleanArray(key);
     }
 
     /**
@@ -1401,18 +1195,9 @@
      * @param key a String, or null
      * @return a byte[] value, or null
      */
+    @Override
     public byte[] getByteArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (byte[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "byte[]", e);
-            return null;
-        }
+        return super.getByteArray(key);
     }
 
     /**
@@ -1423,18 +1208,9 @@
      * @param key a String, or null
      * @return a short[] value, or null
      */
+    @Override
     public short[] getShortArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (short[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "short[]", e);
-            return null;
-        }
+        return super.getShortArray(key);
     }
 
     /**
@@ -1445,18 +1221,9 @@
      * @param key a String, or null
      * @return a char[] value, or null
      */
+    @Override
     public char[] getCharArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (char[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "char[]", e);
-            return null;
-        }
+        return super.getCharArray(key);
     }
 
     /**
@@ -1467,18 +1234,9 @@
      * @param key a String, or null
      * @return an int[] value, or null
      */
+    @Override
     public int[] getIntArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (int[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "int[]", e);
-            return null;
-        }
+        return super.getIntArray(key);
     }
 
     /**
@@ -1489,18 +1247,9 @@
      * @param key a String, or null
      * @return a long[] value, or null
      */
+    @Override
     public long[] getLongArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (long[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "long[]", e);
-            return null;
-        }
+        return super.getLongArray(key);
     }
 
     /**
@@ -1511,18 +1260,9 @@
      * @param key a String, or null
      * @return a float[] value, or null
      */
+    @Override
     public float[] getFloatArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (float[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "float[]", e);
-            return null;
-        }
+        return super.getFloatArray(key);
     }
 
     /**
@@ -1533,18 +1273,9 @@
      * @param key a String, or null
      * @return a double[] value, or null
      */
+    @Override
     public double[] getDoubleArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (double[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "double[]", e);
-            return null;
-        }
+        return super.getDoubleArray(key);
     }
 
     /**
@@ -1555,18 +1286,9 @@
      * @param key a String, or null
      * @return a String[] value, or null
      */
+    @Override
     public String[] getStringArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (String[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "String[]", e);
-            return null;
-        }
+        return super.getStringArray(key);
     }
 
     /**
@@ -1577,18 +1299,9 @@
      * @param key a String, or null
      * @return a CharSequence[] value, or null
      */
+    @Override
     public CharSequence[] getCharSequenceArray(String key) {
-        unparcel();
-        Object o = mMap.get(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (CharSequence[]) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "CharSequence[]", e);
-            return null;
-        }
+        return super.getCharSequenceArray(key);
     }
 
     /**
@@ -1641,10 +1354,12 @@
 
     public static final Parcelable.Creator<Bundle> CREATOR =
         new Parcelable.Creator<Bundle>() {
+        @Override
         public Bundle createFromParcel(Parcel in) {
             return in.readBundle();
         }
 
+        @Override
         public Bundle[] newArray(int size) {
             return new Bundle[size];
         }
@@ -1653,6 +1368,7 @@
     /**
      * Report the nature of this Parcelable's contents
      */
+    @Override
     public int describeContents() {
         int mask = 0;
         if (hasFileDescriptors()) {
@@ -1660,44 +1376,17 @@
         }
         return mask;
     }
-    
+
     /**
      * Writes the Bundle contents to a Parcel, typically in order for
      * it to be passed through an IBinder connection.
      * @param parcel The parcel to copy this bundle to.
      */
+    @Override
     public void writeToParcel(Parcel parcel, int flags) {
         final boolean oldAllowFds = parcel.pushAllowFds(mAllowFds);
         try {
-            if (mParcelledData != null) {
-                if (mParcelledData == EMPTY_PARCEL) {
-                    parcel.writeInt(0);
-                } else {
-                    int length = mParcelledData.dataSize();
-                    parcel.writeInt(length);
-                    parcel.writeInt(BUNDLE_MAGIC);
-                    parcel.appendFrom(mParcelledData, 0, length);
-                }
-            } else {
-                // Special case for empty bundles.
-                if (mMap == null || mMap.size() <= 0) {
-                    parcel.writeInt(0);
-                    return;
-                }
-                int lengthPos = parcel.dataPosition();
-                parcel.writeInt(-1); // dummy, will hold length
-                parcel.writeInt(BUNDLE_MAGIC);
-    
-                int startPos = parcel.dataPosition();
-                parcel.writeArrayMapInternal(mMap);
-                int endPos = parcel.dataPosition();
-    
-                // Backpatch length
-                parcel.setDataPosition(lengthPos);
-                int length = endPos - startPos;
-                parcel.writeInt(length);
-                parcel.setDataPosition(endPos);
-            }
+            super.writeToParcelInner(parcel, flags);
         } finally {
             parcel.restoreAllowFds(oldAllowFds);
         }
@@ -1709,41 +1398,8 @@
      * @param parcel The parcel to overwrite this bundle from.
      */
     public void readFromParcel(Parcel parcel) {
-        int length = parcel.readInt();
-        if (length < 0) {
-            throw new RuntimeException("Bad length in parcel: " + length);
-        }
-        readFromParcelInner(parcel, length);
-    }
-
-    void readFromParcelInner(Parcel parcel, int length) {
-        if (length == 0) {
-            // Empty Bundle or end of data.
-            mParcelledData = EMPTY_PARCEL;
-            mHasFds = false;
-            mFdsKnown = true;
-            return;
-        }
-        int magic = parcel.readInt();
-        if (magic != BUNDLE_MAGIC) {
-            //noinspection ThrowableInstanceNeverThrown
-            throw new IllegalStateException("Bad magic number for Bundle: 0x"
-                    + Integer.toHexString(magic));
-        }
-
-        // Advance within this Parcel
-        int offset = parcel.dataPosition();
-        parcel.setDataPosition(offset + length);
-
-        Parcel p = Parcel.obtain();
-        p.setDataPosition(0);
-        p.appendFrom(parcel, offset, length);
-        if (DEBUG) Log.d(TAG, "Retrieving "  + Integer.toHexString(System.identityHashCode(this))
-                + ": " + length + " bundle bytes starting at " + offset);
-        p.setDataPosition(0);
-
-        mParcelledData = p;
-        mHasFds = p.hasFileDescriptors();
+        super.readFromParcelInner(parcel);
+        mHasFds = mParcelledData.hasFileDescriptors();
         mFdsKnown = true;
     }
 
@@ -1759,4 +1415,5 @@
         }
         return "Bundle[" + mMap.toString() + "]";
     }
+
 }
diff --git a/core/java/android/os/CommonBundle.java b/core/java/android/os/CommonBundle.java
new file mode 100644
index 0000000..e11f170
--- /dev/null
+++ b/core/java/android/os/CommonBundle.java
@@ -0,0 +1,1384 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A mapping from String values to various types.
+ */
+abstract class CommonBundle implements Parcelable, Cloneable {
+    private static final String TAG = "Bundle";
+    static final boolean DEBUG = false;
+
+    static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
+    static final Parcel EMPTY_PARCEL;
+
+    static {
+        EMPTY_PARCEL = Parcel.obtain();
+    }
+
+    // Invariant - exactly one of mMap / mParcelledData will be null
+    // (except inside a call to unparcel)
+
+    ArrayMap<String, Object> mMap = null;
+
+    /*
+     * If mParcelledData is non-null, then mMap will be null and the
+     * data are stored as a Parcel containing a Bundle.  When the data
+     * are unparcelled, mParcelledData willbe set to null.
+     */
+    Parcel mParcelledData = null;
+
+    /**
+     * The ClassLoader used when unparcelling data from mParcelledData.
+     */
+    private ClassLoader mClassLoader;
+
+    /**
+     * Constructs a new, empty Bundle that uses a specific ClassLoader for
+     * instantiating Parcelable and Serializable objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     * @param capacity Initial size of the ArrayMap.
+     */
+    CommonBundle(ClassLoader loader, int capacity) {
+        mMap = capacity > 0 ?
+                new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();
+        mClassLoader = loader == null ? getClass().getClassLoader() : loader;
+    }
+
+    /**
+     * Constructs a new, empty Bundle.
+     */
+    CommonBundle() {
+        this((ClassLoader) null, 0);
+    }
+
+    /**
+     * Constructs a Bundle whose data is stored as a Parcel.  The data
+     * will be unparcelled on first contact, using the assigned ClassLoader.
+     *
+     * @param parcelledData a Parcel containing a Bundle
+     */
+    CommonBundle(Parcel parcelledData) {
+        readFromParcelInner(parcelledData);
+    }
+
+    CommonBundle(Parcel parcelledData, int length) {
+        readFromParcelInner(parcelledData, length);
+    }
+
+    /**
+     * Constructs a new, empty Bundle that uses a specific ClassLoader for
+     * instantiating Parcelable and Serializable objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     */
+    CommonBundle(ClassLoader loader) {
+        this(loader, 0);
+    }
+
+    /**
+     * Constructs a new, empty Bundle sized to hold the given number of
+     * elements. The Bundle will grow as needed.
+     *
+     * @param capacity the initial capacity of the Bundle
+     */
+    CommonBundle(int capacity) {
+        this((ClassLoader) null, capacity);
+    }
+
+    /**
+     * Constructs a Bundle containing a copy of the mappings from the given
+     * Bundle.
+     *
+     * @param b a Bundle to be copied.
+     */
+    CommonBundle(CommonBundle b) {
+        if (b.mParcelledData != null) {
+            if (b.mParcelledData == EMPTY_PARCEL) {
+                mParcelledData = EMPTY_PARCEL;
+            } else {
+                mParcelledData = Parcel.obtain();
+                mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize());
+                mParcelledData.setDataPosition(0);
+            }
+        } else {
+            mParcelledData = null;
+        }
+
+        if (b.mMap != null) {
+            mMap = new ArrayMap<String, Object>(b.mMap);
+        } else {
+            mMap = null;
+        }
+
+        mClassLoader = b.mClassLoader;
+    }
+
+    /**
+     * TODO: optimize this later (getting just the value part of a Bundle
+     * with a single pair) once Bundle.forPair() above is implemented
+     * with a special single-value Map implementation/serialization.
+     *
+     * Note: value in single-pair Bundle may be null.
+     *
+     * @hide
+     */
+    String getPairValue() {
+        unparcel();
+        int size = mMap.size();
+        if (size > 1) {
+            Log.w(TAG, "getPairValue() used on Bundle with multiple pairs.");
+        }
+        if (size == 0) {
+            return null;
+        }
+        Object o = mMap.valueAt(0);
+        try {
+            return (String) o;
+        } catch (ClassCastException e) {
+            typeWarning("getPairValue()", o, "String", e);
+            return null;
+        }
+    }
+
+    /**
+     * Changes the ClassLoader this Bundle uses when instantiating objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the Bundle.
+     */
+    void setClassLoader(ClassLoader loader) {
+        mClassLoader = loader;
+    }
+
+    /**
+     * Return the ClassLoader currently associated with this Bundle.
+     */
+    ClassLoader getClassLoader() {
+        return mClassLoader;
+    }
+
+    /**
+     * If the underlying data are stored as a Parcel, unparcel them
+     * using the currently assigned class loader.
+     */
+    /* package */ synchronized void unparcel() {
+        if (mParcelledData == null) {
+            if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+                    + ": no parcelled data");
+            return;
+        }
+
+        if (mParcelledData == EMPTY_PARCEL) {
+            if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+                    + ": empty");
+            if (mMap == null) {
+                mMap = new ArrayMap<String, Object>(1);
+            } else {
+                mMap.erase();
+            }
+            mParcelledData = null;
+            return;
+        }
+
+        int N = mParcelledData.readInt();
+        if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+                + ": reading " + N + " maps");
+        if (N < 0) {
+            return;
+        }
+        if (mMap == null) {
+            mMap = new ArrayMap<String, Object>(N);
+        } else {
+            mMap.erase();
+            mMap.ensureCapacity(N);
+        }
+        mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);
+        mParcelledData.recycle();
+        mParcelledData = null;
+        if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+                + " final map: " + mMap);
+    }
+
+    /**
+     * @hide
+     */
+    boolean isParcelled() {
+        return mParcelledData != null;
+    }
+
+    /**
+     * Returns the number of mappings contained in this Bundle.
+     *
+     * @return the number of mappings as an int.
+     */
+    int size() {
+        unparcel();
+        return mMap.size();
+    }
+
+    /**
+     * Returns true if the mapping of this Bundle is empty, false otherwise.
+     */
+    boolean isEmpty() {
+        unparcel();
+        return mMap.isEmpty();
+    }
+
+    /**
+     * Removes all elements from the mapping of this Bundle.
+     */
+    void clear() {
+        unparcel();
+        mMap.clear();
+    }
+
+    /**
+     * Returns true if the given key is contained in the mapping
+     * of this Bundle.
+     *
+     * @param key a String key
+     * @return true if the key is part of the mapping, false otherwise
+     */
+    boolean containsKey(String key) {
+        unparcel();
+        return mMap.containsKey(key);
+    }
+
+    /**
+     * Returns the entry with the given key as an object.
+     *
+     * @param key a String key
+     * @return an Object, or null
+     */
+    Object get(String key) {
+        unparcel();
+        return mMap.get(key);
+    }
+
+    /**
+     * Removes any entry with the given key from the mapping of this Bundle.
+     *
+     * @param key a String key
+     */
+    void remove(String key) {
+        unparcel();
+        mMap.remove(key);
+    }
+
+    /**
+     * Inserts all mappings from the given PersistableBundle into this CommonBundle.
+     *
+     * @param bundle a PersistableBundle
+     */
+    void putAll(PersistableBundle bundle) {
+        unparcel();
+        bundle.unparcel();
+        mMap.putAll(bundle.mMap);
+    }
+
+    /**
+     * Returns a Set containing the Strings used as keys in this Bundle.
+     *
+     * @return a Set of String keys
+     */
+    Set<String> keySet() {
+        unparcel();
+        return mMap.keySet();
+    }
+
+    /**
+     * Inserts a Boolean value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Boolean, or null
+     */
+    void putBoolean(String key, boolean value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a byte value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a byte
+     */
+    void putByte(String key, byte value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a char value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a char, or null
+     */
+    void putChar(String key, char value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a short value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a short
+     */
+    void putShort(String key, short value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an int value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value an int, or null
+     */
+    void putInt(String key, int value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a long value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a long
+     */
+    void putLong(String key, long value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a float value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a float
+     */
+    void putFloat(String key, float value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a double value into the mapping of this Bundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a double
+     */
+    void putDouble(String key, double value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a String value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a String, or null
+     */
+    void putString(String key, String value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a CharSequence value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a CharSequence, or null
+     */
+    void putCharSequence(String key, CharSequence value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an ArrayList<Integer> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<Integer> object, or null
+     */
+    void putIntegerArrayList(String key, ArrayList<Integer> value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an ArrayList<String> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<String> object, or null
+     */
+    void putStringArrayList(String key, ArrayList<String> value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an ArrayList<CharSequence> value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an ArrayList<CharSequence> object, or null
+     */
+    void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a Serializable value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Serializable object, or null
+     */
+    void putSerializable(String key, Serializable value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a boolean array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a boolean array object, or null
+     */
+    void putBooleanArray(String key, boolean[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a byte array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a byte array object, or null
+     */
+    void putByteArray(String key, byte[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a short array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a short array object, or null
+     */
+    void putShortArray(String key, short[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a char array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a char array object, or null
+     */
+    void putCharArray(String key, char[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts an int array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an int array object, or null
+     */
+    void putIntArray(String key, int[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a long array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a long array object, or null
+     */
+    void putLongArray(String key, long[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a float array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a float array object, or null
+     */
+    void putFloatArray(String key, float[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a double array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a double array object, or null
+     */
+    void putDoubleArray(String key, double[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a String array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a String array object, or null
+     */
+    void putStringArray(String key, String[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a CharSequence array value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a CharSequence array object, or null
+     */
+    void putCharSequenceArray(String key, CharSequence[] value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Bundle object, or null
+     */
+    void putPersistableBundle(String key, PersistableBundle value) {
+        unparcel();
+        mMap.put(key, value);
+    }
+
+    /**
+     * Returns the value associated with the given key, or false if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a boolean value
+     */
+    boolean getBoolean(String key) {
+        unparcel();
+        if (DEBUG) Log.d(TAG, "Getting boolean in "
+                + Integer.toHexString(System.identityHashCode(this)));
+        return getBoolean(key, false);
+    }
+
+    // Log a message if the value was non-null but not of the expected type
+    void typeWarning(String key, Object value, String className,
+            Object defaultValue, ClassCastException e) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Key ");
+        sb.append(key);
+        sb.append(" expected ");
+        sb.append(className);
+        sb.append(" but value was a ");
+        sb.append(value.getClass().getName());
+        sb.append(".  The default value ");
+        sb.append(defaultValue);
+        sb.append(" was returned.");
+        Log.w(TAG, sb.toString());
+        Log.w(TAG, "Attempt to cast generated internal exception:", e);
+    }
+
+    void typeWarning(String key, Object value, String className,
+            ClassCastException e) {
+        typeWarning(key, value, className, "<null>", e);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a boolean value
+     */
+    boolean getBoolean(String key, boolean defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Boolean) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Boolean", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or (byte) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a byte value
+     */
+    byte getByte(String key) {
+        unparcel();
+        return getByte(key, (byte) 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a byte value
+     */
+    Byte getByte(String key, byte defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Byte) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Byte", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or (char) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a char value
+     */
+    char getChar(String key) {
+        unparcel();
+        return getChar(key, (char) 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a char value
+     */
+    char getChar(String key, char defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Character) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Character", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or (short) 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a short value
+     */
+    short getShort(String key) {
+        unparcel();
+        return getShort(key, (short) 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a short value
+     */
+    short getShort(String key, short defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Short) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Short", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return an int value
+     */
+    int getInt(String key) {
+        unparcel();
+        return getInt(key, 0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return an int value
+     */
+    int getInt(String key, int defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Integer) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Integer", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0L if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a long value
+     */
+    long getLong(String key) {
+        unparcel();
+        return getLong(key, 0L);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a long value
+     */
+    long getLong(String key, long defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Long) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Long", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0f if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a float value
+     */
+    float getFloat(String key) {
+        unparcel();
+        return getFloat(key, 0.0f);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a float value
+     */
+    float getFloat(String key, float defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Float) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Float", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a double value
+     */
+    double getDouble(String key) {
+        unparcel();
+        return getDouble(key, 0.0);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a double value
+     */
+    double getDouble(String key, double defaultValue) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return defaultValue;
+        }
+        try {
+            return (Double) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Double", defaultValue, e);
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a String value, or null
+     */
+    String getString(String key) {
+        unparcel();
+        final Object o = mMap.get(key);
+        try {
+            return (String) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "String", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String, or null
+     * @param defaultValue Value to return if key does not exist
+     * @return the String value associated with the given key, or defaultValue
+     *     if no valid String object is currently mapped to that key.
+     */
+    String getString(String key, String defaultValue) {
+        final String s = getString(key);
+        return (s == null) ? defaultValue : s;
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a CharSequence value, or null
+     */
+    CharSequence getCharSequence(String key) {
+        unparcel();
+        final Object o = mMap.get(key);
+        try {
+            return (CharSequence) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "CharSequence", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String, or null
+     * @param defaultValue Value to return if key does not exist
+     * @return the CharSequence value associated with the given key, or defaultValue
+     *     if no valid CharSequence object is currently mapped to that key.
+     */
+    CharSequence getCharSequence(String key, CharSequence defaultValue) {
+        final CharSequence cs = getCharSequence(key);
+        return (cs == null) ? defaultValue : cs;
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Bundle value, or null
+     */
+    PersistableBundle getPersistableBundle(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (PersistableBundle) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Bundle", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Serializable value, or null
+     */
+    Serializable getSerializable(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (Serializable) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "Serializable", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<String> value, or null
+     */
+    ArrayList<Integer> getIntegerArrayList(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<Integer>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList<Integer>", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<String> value, or null
+     */
+    ArrayList<String> getStringArrayList(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<String>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList<String>", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an ArrayList<CharSequence> value, or null
+     */
+    ArrayList<CharSequence> getCharSequenceArrayList(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (ArrayList<CharSequence>) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "ArrayList<CharSequence>", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a boolean[] value, or null
+     */
+    boolean[] getBooleanArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (boolean[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "byte[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a byte[] value, or null
+     */
+    byte[] getByteArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (byte[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "byte[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a short[] value, or null
+     */
+    short[] getShortArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (short[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "short[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a char[] value, or null
+     */
+    char[] getCharArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (char[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "char[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an int[] value, or null
+     */
+    int[] getIntArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (int[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "int[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a long[] value, or null
+     */
+    long[] getLongArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (long[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "long[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a float[] value, or null
+     */
+    float[] getFloatArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (float[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "float[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a double[] value, or null
+     */
+    double[] getDoubleArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (double[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "double[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a String[] value, or null
+     */
+    String[] getStringArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (String[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "String[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a CharSequence[] value, or null
+     */
+    CharSequence[] getCharSequenceArray(String key) {
+        unparcel();
+        Object o = mMap.get(key);
+        if (o == null) {
+            return null;
+        }
+        try {
+            return (CharSequence[]) o;
+        } catch (ClassCastException e) {
+            typeWarning(key, o, "CharSequence[]", e);
+            return null;
+        }
+    }
+
+    /**
+     * Writes the Bundle contents to a Parcel, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to copy this bundle to.
+     */
+    void writeToParcelInner(Parcel parcel, int flags) {
+        if (mParcelledData != null) {
+            if (mParcelledData == EMPTY_PARCEL) {
+                parcel.writeInt(0);
+            } else {
+                int length = mParcelledData.dataSize();
+                parcel.writeInt(length);
+                parcel.writeInt(BUNDLE_MAGIC);
+                parcel.appendFrom(mParcelledData, 0, length);
+            }
+        } else {
+            // Special case for empty bundles.
+            if (mMap == null || mMap.size() <= 0) {
+                parcel.writeInt(0);
+                return;
+            }
+            int lengthPos = parcel.dataPosition();
+            parcel.writeInt(-1); // dummy, will hold length
+            parcel.writeInt(BUNDLE_MAGIC);
+
+            int startPos = parcel.dataPosition();
+            parcel.writeArrayMapInternal(mMap);
+            int endPos = parcel.dataPosition();
+
+            // Backpatch length
+            parcel.setDataPosition(lengthPos);
+            int length = endPos - startPos;
+            parcel.writeInt(length);
+            parcel.setDataPosition(endPos);
+        }
+    }
+
+    /**
+     * Reads the Parcel contents into this Bundle, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to overwrite this bundle from.
+     */
+    void readFromParcelInner(Parcel parcel) {
+        int length = parcel.readInt();
+        if (length < 0) {
+            throw new RuntimeException("Bad length in parcel: " + length);
+        }
+        readFromParcelInner(parcel, length);
+    }
+
+    private void readFromParcelInner(Parcel parcel, int length) {
+        if (length == 0) {
+            // Empty Bundle or end of data.
+            mParcelledData = EMPTY_PARCEL;
+            return;
+        }
+        int magic = parcel.readInt();
+        if (magic != BUNDLE_MAGIC) {
+            //noinspection ThrowableInstanceNeverThrown
+            throw new IllegalStateException("Bad magic number for Bundle: 0x"
+                    + Integer.toHexString(magic));
+        }
+
+        // Advance within this Parcel
+        int offset = parcel.dataPosition();
+        parcel.setDataPosition(offset + length);
+
+        Parcel p = Parcel.obtain();
+        p.setDataPosition(0);
+        p.appendFrom(parcel, offset, length);
+        if (DEBUG) Log.d(TAG, "Retrieving "  + Integer.toHexString(System.identityHashCode(this))
+                + ": " + length + " bundle bytes starting at " + offset);
+        p.setDataPosition(0);
+
+        mParcelledData = p;
+    }
+}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index dc18dee..1089f27 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -370,8 +370,8 @@
      * attacks.
      */
     public static boolean contains(File dir, File file) {
-        String dirPath = dir.getPath();
-        String filePath = file.getPath();
+        String dirPath = dir.getAbsolutePath();
+        String filePath = file.getAbsolutePath();
 
         if (dirPath.equals(filePath)) {
             return true;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 8e0ff08..95cb9f3 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -223,6 +223,7 @@
     private static final int VAL_SPARSEBOOLEANARRAY = 22;
     private static final int VAL_BOOLEANARRAY = 23;
     private static final int VAL_CHARSEQUENCEARRAY = 24;
+    private static final int VAL_PERSISTABLEBUNDLE = 25;
 
     // The initial int32 in a Binder call's reply Parcel header:
     private static final int EX_SECURITY = -1;
@@ -638,6 +639,19 @@
     }
 
     /**
+     * Flatten a PersistableBundle into the parcel at the current dataPosition(),
+     * growing dataCapacity() if needed.
+     */
+    public final void writePersistableBundle(PersistableBundle val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+
+        val.writeToParcel(this, 0);
+    }
+
+    /**
      * Flatten a List into the parcel at the current dataPosition(), growing
      * dataCapacity() if needed.  The List values are written using
      * {@link #writeValue} and must follow the specification there.
@@ -1256,6 +1270,9 @@
         } else if (v instanceof Byte) {
             writeInt(VAL_BYTE);
             writeInt((Byte) v);
+        } else if (v instanceof PersistableBundle) {
+            writeInt(VAL_PERSISTABLEBUNDLE);
+            writePersistableBundle((PersistableBundle) v);
         } else {
             Class<?> clazz = v.getClass();
             if (clazz.isArray() && clazz.getComponentType() == Object.class) {
@@ -1633,6 +1650,35 @@
     }
 
     /**
+     * Read and return a new Bundle object from the parcel at the current
+     * dataPosition().  Returns null if the previously written Bundle object was
+     * null.
+     */
+    public final PersistableBundle readPersistableBundle() {
+        return readPersistableBundle(null);
+    }
+
+    /**
+     * Read and return a new Bundle object from the parcel at the current
+     * dataPosition(), using the given class loader to initialize the class
+     * loader of the Bundle for later retrieval of Parcelable objects.
+     * Returns null if the previously written Bundle object was null.
+     */
+    public final PersistableBundle readPersistableBundle(ClassLoader loader) {
+        int length = readInt();
+        if (length < 0) {
+            if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);
+            return null;
+        }
+
+        final PersistableBundle bundle = new PersistableBundle(this, length);
+        if (loader != null) {
+            bundle.setClassLoader(loader);
+        }
+        return bundle;
+    }
+
+    /**
      * Read and return a byte[] object from the parcel.
      */
     public final byte[] createByteArray() {
@@ -2082,6 +2128,9 @@
         case VAL_BUNDLE:
             return readBundle(loader); // loading will be deferred
 
+        case VAL_PERSISTABLEBUNDLE:
+            return readPersistableBundle(loader);
+
         default:
             int off = dataPosition() - 4;
             throw new RuntimeException(
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 86dc8b4..24bf05e 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -42,6 +42,7 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.net.DatagramSocket;
 import java.net.Socket;
 import java.nio.ByteOrder;
@@ -698,6 +699,9 @@
             } catch (ErrnoException e) {
                 // Reporting status is best-effort
                 Log.w(TAG, "Failed to report status: " + e);
+            } catch (InterruptedIOException e) {
+                // Reporting status is best-effort
+                Log.w(TAG, "Failed to report status: " + e);
             }
 
         } finally {
@@ -728,6 +732,9 @@
                 Log.d(TAG, "Failed to read status; assuming dead: " + e);
                 return new Status(Status.DEAD);
             }
+        } catch (InterruptedIOException e) {
+            Log.d(TAG, "Failed to read status; assuming dead: " + e);
+            return new Status(Status.DEAD);
         }
     }
 
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
new file mode 100644
index 0000000..c2cd3be
--- /dev/null
+++ b/core/java/android/os/PersistableBundle.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.ArrayMap;
+
+import java.util.Set;
+
+/**
+ * A mapping from String values to various types that can be saved to persistent and later
+ * restored.
+ *
+ */
+public final class PersistableBundle extends CommonBundle {
+    public static final PersistableBundle EMPTY;
+    static final Parcel EMPTY_PARCEL;
+
+    static {
+        EMPTY = new PersistableBundle();
+        EMPTY.mMap = ArrayMap.EMPTY;
+        EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
+    }
+
+    /**
+     * Constructs a new, empty PersistableBundle.
+     */
+    public PersistableBundle() {
+        super();
+    }
+
+    /**
+     * Constructs a PersistableBundle whose data is stored as a Parcel.  The data
+     * will be unparcelled on first contact, using the assigned ClassLoader.
+     *
+     * @param parcelledData a Parcel containing a PersistableBundle
+     */
+    PersistableBundle(Parcel parcelledData) {
+        super(parcelledData);
+    }
+
+    /* package */ PersistableBundle(Parcel parcelledData, int length) {
+        super(parcelledData, length);
+    }
+
+    /**
+     * Constructs a new, empty PersistableBundle that uses a specific ClassLoader for
+     * instantiating Parcelable and Serializable objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the PersistableBundle.
+     */
+    public PersistableBundle(ClassLoader loader) {
+        super(loader);
+    }
+
+    /**
+     * Constructs a new, empty PersistableBundle sized to hold the given number of
+     * elements. The PersistableBundle will grow as needed.
+     *
+     * @param capacity the initial capacity of the PersistableBundle
+     */
+    public PersistableBundle(int capacity) {
+        super(capacity);
+    }
+
+    /**
+     * Constructs a PersistableBundle containing a copy of the mappings from the given
+     * PersistableBundle.
+     *
+     * @param b a PersistableBundle to be copied.
+     */
+    public PersistableBundle(PersistableBundle b) {
+        super(b);
+    }
+
+    /**
+     * Make a PersistableBundle for a single key/value pair.
+     *
+     * @hide
+     */
+    public static PersistableBundle forPair(String key, String value) {
+        PersistableBundle b = new PersistableBundle(1);
+        b.putString(key, value);
+        return b;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public String getPairValue() {
+        return super.getPairValue();
+    }
+
+    /**
+     * Changes the ClassLoader this PersistableBundle uses when instantiating objects.
+     *
+     * @param loader An explicit ClassLoader to use when instantiating objects
+     * inside of the PersistableBundle.
+     */
+    @Override
+    public void setClassLoader(ClassLoader loader) {
+        super.setClassLoader(loader);
+    }
+
+    /**
+     * Return the ClassLoader currently associated with this PersistableBundle.
+     */
+    @Override
+    public ClassLoader getClassLoader() {
+        return super.getClassLoader();
+    }
+
+    /**
+     * Clones the current PersistableBundle. The internal map is cloned, but the keys and
+     * values to which it refers are copied by reference.
+     */
+    @Override
+    public Object clone() {
+        return new PersistableBundle(this);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean isParcelled() {
+        return super.isParcelled();
+    }
+
+    /**
+     * Returns the number of mappings contained in this PersistableBundle.
+     *
+     * @return the number of mappings as an int.
+     */
+    @Override
+    public int size() {
+        return super.size();
+    }
+
+    /**
+     * Returns true if the mapping of this PersistableBundle is empty, false otherwise.
+     */
+    @Override
+    public boolean isEmpty() {
+        return super.isEmpty();
+    }
+
+    /**
+     * Removes all elements from the mapping of this PersistableBundle.
+     */
+    @Override
+    public void clear() {
+        super.clear();
+    }
+
+    /**
+     * Returns true if the given key is contained in the mapping
+     * of this PersistableBundle.
+     *
+     * @param key a String key
+     * @return true if the key is part of the mapping, false otherwise
+     */
+    @Override
+    public boolean containsKey(String key) {
+        return super.containsKey(key);
+    }
+
+    /**
+     * Returns the entry with the given key as an object.
+     *
+     * @param key a String key
+     * @return an Object, or null
+     */
+    @Override
+    public Object get(String key) {
+        return super.get(key);
+    }
+
+    /**
+     * Removes any entry with the given key from the mapping of this PersistableBundle.
+     *
+     * @param key a String key
+     */
+    @Override
+    public void remove(String key) {
+        super.remove(key);
+    }
+
+    /**
+     * Inserts all mappings from the given PersistableBundle into this Bundle.
+     *
+     * @param bundle a PersistableBundle
+     */
+    public void putAll(PersistableBundle bundle) {
+        super.putAll(bundle);
+    }
+
+    /**
+     * Returns a Set containing the Strings used as keys in this PersistableBundle.
+     *
+     * @return a Set of String keys
+     */
+    @Override
+    public Set<String> keySet() {
+        return super.keySet();
+    }
+
+    /**
+     * Inserts an int value into the mapping of this PersistableBundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value an int, or null
+     */
+    @Override
+    public void putInt(String key, int value) {
+        super.putInt(key, value);
+    }
+
+    /**
+     * Inserts a long value into the mapping of this PersistableBundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a long
+     */
+    @Override
+    public void putLong(String key, long value) {
+        super.putLong(key, value);
+    }
+
+    /**
+     * Inserts a double value into the mapping of this PersistableBundle, replacing
+     * any existing value for the given key.
+     *
+     * @param key a String, or null
+     * @param value a double
+     */
+    @Override
+    public void putDouble(String key, double value) {
+        super.putDouble(key, value);
+    }
+
+    /**
+     * Inserts a String value into the mapping of this PersistableBundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a String, or null
+     */
+    @Override
+    public void putString(String key, String value) {
+        super.putString(key, value);
+    }
+
+    /**
+     * Inserts an int array value into the mapping of this PersistableBundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value an int array object, or null
+     */
+    @Override
+    public void putIntArray(String key, int[] value) {
+        super.putIntArray(key, value);
+    }
+
+    /**
+     * Inserts a long array value into the mapping of this PersistableBundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a long array object, or null
+     */
+    @Override
+    public void putLongArray(String key, long[] value) {
+        super.putLongArray(key, value);
+    }
+
+    /**
+     * Inserts a double array value into the mapping of this PersistableBundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a double array object, or null
+     */
+    @Override
+    public void putDoubleArray(String key, double[] value) {
+        super.putDoubleArray(key, value);
+    }
+
+    /**
+     * Inserts a String array value into the mapping of this PersistableBundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a String array object, or null
+     */
+    @Override
+    public void putStringArray(String key, String[] value) {
+        super.putStringArray(key, value);
+    }
+
+    /**
+     * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
+     * any existing value for the given key.  Either key or value may be null.
+     *
+     * @param key a String, or null
+     * @param value a Bundle object, or null
+     */
+    public void putPersistableBundle(String key, PersistableBundle value) {
+        super.putPersistableBundle(key, value);
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return an int value
+     */
+    @Override
+    public int getInt(String key) {
+        return super.getInt(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return an int value
+     */
+    @Override
+    public int getInt(String key, int defaultValue) {
+        return super.getInt(key, defaultValue);
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0L if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a long value
+     */
+    @Override
+    public long getLong(String key) {
+        return super.getLong(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a long value
+     */
+    @Override
+    public long getLong(String key, long defaultValue) {
+        return super.getLong(key, defaultValue);
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0 if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @return a double value
+     */
+    @Override
+    public double getDouble(String key) {
+        return super.getDouble(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String
+     * @param defaultValue Value to return if key does not exist
+     * @return a double value
+     */
+    @Override
+    public double getDouble(String key, double defaultValue) {
+        return super.getDouble(key, defaultValue);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a String value, or null
+     */
+    @Override
+    public String getString(String key) {
+        return super.getString(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or defaultValue if
+     * no mapping of the desired type exists for the given key.
+     *
+     * @param key a String, or null
+     * @param defaultValue Value to return if key does not exist
+     * @return the String value associated with the given key, or defaultValue
+     *     if no valid String object is currently mapped to that key.
+     */
+    @Override
+    public String getString(String key, String defaultValue) {
+        return super.getString(key, defaultValue);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a Bundle value, or null
+     */
+    @Override
+    public PersistableBundle getPersistableBundle(String key) {
+        return super.getPersistableBundle(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return an int[] value, or null
+     */
+    @Override
+    public int[] getIntArray(String key) {
+        return super.getIntArray(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a long[] value, or null
+     */
+    @Override
+    public long[] getLongArray(String key) {
+        return super.getLongArray(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a double[] value, or null
+     */
+    @Override
+    public double[] getDoubleArray(String key) {
+        return super.getDoubleArray(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if
+     * no mapping of the desired type exists for the given key or a null
+     * value is explicitly associated with the key.
+     *
+     * @param key a String, or null
+     * @return a String[] value, or null
+     */
+    @Override
+    public String[] getStringArray(String key) {
+        return super.getStringArray(key);
+    }
+
+    public static final Parcelable.Creator<PersistableBundle> CREATOR =
+            new Parcelable.Creator<PersistableBundle>() {
+                @Override
+                public PersistableBundle createFromParcel(Parcel in) {
+                    return in.readPersistableBundle();
+                }
+
+                @Override
+                public PersistableBundle[] newArray(int size) {
+                    return new PersistableBundle[size];
+                }
+            };
+
+    /**
+     * Report the nature of this Parcelable's contents
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Writes the PersistableBundle contents to a Parcel, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to copy this bundle to.
+     */
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        final boolean oldAllowFds = parcel.pushAllowFds(false);
+        try {
+            super.writeToParcelInner(parcel, flags);
+        } finally {
+            parcel.restoreAllowFds(oldAllowFds);
+        }
+    }
+
+    /**
+     * Reads the Parcel contents into this PersistableBundle, typically in order for
+     * it to be passed through an IBinder connection.
+     * @param parcel The parcel to overwrite this bundle from.
+     */
+    public void readFromParcel(Parcel parcel) {
+        super.readFromParcelInner(parcel);
+    }
+
+    @Override
+    synchronized public String toString() {
+        if (mParcelledData != null) {
+            if (mParcelledData == EMPTY_PARCEL) {
+                return "PersistableBundle[EMPTY_PARCEL]";
+            } else {
+                return "PersistableBundle[mParcelledData.dataSize=" +
+                        mParcelledData.dataSize() + "]";
+            }
+        }
+        return "PersistableBundle[" + mMap.toString() + "]";
+    }
+
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 8aef9bd..7bac3af 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -300,7 +300,8 @@
 
     /**
      * Sets all the user-wide restrictions for this user.
-     * Requires the MANAGE_USERS permission.
+     * Requires the MANAGE_USERS permission or profile/device owner
+     * privileges.
      * @param restrictions the Bundle containing all the restrictions.
      */
     public void setUserRestrictions(Bundle restrictions) {
@@ -309,7 +310,8 @@
 
     /**
      * Sets all the user-wide restrictions for the specified user.
-     * Requires the MANAGE_USERS permission.
+     * Requires the MANAGE_USERS permission or profile/device owner
+     * privileges.
      * @param restrictions the Bundle containing all the restrictions.
      * @param userHandle the UserHandle of the user for whom to set the restrictions.
      */
@@ -323,7 +325,8 @@
 
     /**
      * Sets the value of a specific restriction.
-     * Requires the MANAGE_USERS permission.
+     * Requires the MANAGE_USERS permission or profile/device owner
+     * privileges.
      * @param key the key of the restriction
      * @param value the value for the restriction
      */
@@ -336,7 +339,8 @@
     /**
      * @hide
      * Sets the value of a specific restriction on a specific user.
-     * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission or profile/device owner
+     * privileges.
      * @param key the key of the restriction
      * @param value the value for the restriction
      * @param userHandle the user whose restriction is to be changed.
@@ -548,7 +552,7 @@
     /**
      * Returns information for all users on this device. Requires
      * {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * 
+     *
      * @param excludeDying specify if the list should exclude users being
      *            removed.
      * @return the list of users that were created.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index f0520b5..9a768e0 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -57,6 +57,10 @@
  * <p>
  * To create a document provider, extend {@link DocumentsProvider}, which
  * provides a foundational implementation of this contract.
+ * <p>
+ * All client apps must hold a valid URI permission grant to access documents,
+ * typically issued when a user makes a selection through
+ * {@link Intent#ACTION_OPEN_DOCUMENT} or {@link Intent#ACTION_CREATE_DOCUMENT}.
  *
  * @see DocumentsProvider
  */
@@ -69,6 +73,8 @@
     // content://com.example/root/sdcard/search/?query=pony
     // content://com.example/document/12/
     // content://com.example/document/12/children/
+    // content://com.example/via/12/document/24/
+    // content://com.example/via/12/document/24/children/
 
     private DocumentsContract() {
     }
@@ -425,6 +431,14 @@
         public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
 
         /**
+         * Flag indicating that this root supports directory selection.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsProvider#isChildDocument(String, String)
+         */
+        public static final int FLAG_SUPPORTS_DIR_SELECTION = 1 << 4;
+
+        /**
          * 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
@@ -484,12 +498,15 @@
 
     /** {@hide} */
     public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
+    /** {@hide} */
+    public static final String EXTRA_URI = "uri";
 
     private static final String PATH_ROOT = "root";
     private static final String PATH_RECENT = "recent";
     private static final String PATH_DOCUMENT = "document";
     private static final String PATH_CHILDREN = "children";
     private static final String PATH_SEARCH = "search";
+    private static final String PATH_VIA = "via";
 
     private static final String PARAM_QUERY = "query";
     private static final String PARAM_MANAGE = "manage";
@@ -532,6 +549,17 @@
     }
 
     /**
+     * Build URI representing access to descendant documents of the given
+     * {@link Document#COLUMN_DOCUMENT_ID}.
+     *
+     * @see #getViaDocumentId(Uri)
+     */
+    public static Uri buildViaUri(String authority, String documentId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+                .appendPath(PATH_VIA).appendPath(documentId).build();
+    }
+
+    /**
      * Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
      * document provider. When queried, a provider will return a single row with
      * columns defined by {@link Document}.
@@ -545,6 +573,41 @@
     }
 
     /**
+     * Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
+     * document provider. Instead of directly accessing the target document,
+     * gain access via another document. The target document must be a
+     * descendant (child, grandchild, etc) of the via document.
+     * <p>
+     * This is typically used to access documents under a user-selected
+     * directory, since it doesn't require the user to separately confirm each
+     * new document access.
+     *
+     * @param viaUri a related document (directory) that the caller is
+     *            leveraging to gain access to the target document. The target
+     *            document must be a descendant of this directory.
+     * @param documentId the target document, which the caller may not have
+     *            direct access to.
+     * @see Intent#ACTION_PICK_DIRECTORY
+     * @see DocumentsProvider#isChildDocument(String, String)
+     * @see #buildDocumentUri(String, String)
+     */
+    public static Uri buildDocumentViaUri(Uri viaUri, String documentId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(viaUri.getAuthority()).appendPath(PATH_VIA)
+                .appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT)
+                .appendPath(documentId).build();
+    }
+
+    /** {@hide} */
+    public static Uri buildDocumentMaybeViaUri(Uri baseUri, String documentId) {
+        if (isViaUri(baseUri)) {
+            return buildDocumentViaUri(baseUri, documentId);
+        } else {
+            return buildDocumentUri(baseUri.getAuthority(), documentId);
+        }
+    }
+
+    /**
      * Build URI representing the children of the given directory in a document
      * provider. When queried, a provider will return zero or more rows with
      * columns defined by {@link Document}.
@@ -562,6 +625,32 @@
     }
 
     /**
+     * Build URI representing the children of the given directory in a document
+     * provider. Instead of directly accessing the target document, gain access
+     * via another document. The target document must be a descendant (child,
+     * grandchild, etc) of the via document.
+     * <p>
+     * This is typically used to access documents under a user-selected
+     * directory, since it doesn't require the user to separately confirm each
+     * new document access.
+     *
+     * @param viaUri a related document (directory) that the caller is
+     *            leveraging to gain access to the target document. The target
+     *            document must be a descendant of this directory.
+     * @param parentDocumentId the target document, which the caller may not
+     *            have direct access to.
+     * @see Intent#ACTION_PICK_DIRECTORY
+     * @see DocumentsProvider#isChildDocument(String, String)
+     * @see #buildChildDocumentsUri(String, String)
+     */
+    public static Uri buildChildDocumentsViaUri(Uri viaUri, String parentDocumentId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(viaUri.getAuthority()).appendPath(PATH_VIA)
+                .appendPath(getViaDocumentId(viaUri)).appendPath(PATH_DOCUMENT)
+                .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
+    }
+
+    /**
      * Build URI representing a search for matching documents under a specific
      * root in a document provider. When queried, a provider will return zero or
      * more rows with columns defined by {@link Document}.
@@ -580,21 +669,31 @@
     /**
      * Test if the given URI represents a {@link Document} backed by a
      * {@link DocumentsProvider}.
+     *
+     * @see #buildDocumentUri(String, String)
+     * @see #buildDocumentViaUri(Uri, String)
      */
     public static boolean isDocumentUri(Context context, Uri uri) {
         final List<String> paths = uri.getPathSegments();
-        if (paths.size() < 2) {
-            return false;
+        if (paths.size() >= 2
+                && (PATH_DOCUMENT.equals(paths.get(0)) || PATH_VIA.equals(paths.get(0)))) {
+            return isDocumentsProvider(context, uri.getAuthority());
         }
-        if (!PATH_DOCUMENT.equals(paths.get(0))) {
-            return false;
-        }
+        return false;
+    }
 
+    /** {@hide} */
+    public static boolean isViaUri(Uri uri) {
+        final List<String> paths = uri.getPathSegments();
+        return (paths.size() >= 2 && PATH_VIA.equals(paths.get(0)));
+    }
+
+    private static boolean isDocumentsProvider(Context context, String authority) {
         final Intent intent = new Intent(PROVIDER_INTERFACE);
         final List<ResolveInfo> infos = context.getPackageManager()
                 .queryIntentContentProviders(intent, 0);
         for (ResolveInfo info : infos) {
-            if (uri.getAuthority().equals(info.providerInfo.authority)) {
+            if (authority.equals(info.providerInfo.authority)) {
                 return true;
             }
         }
@@ -606,27 +705,40 @@
      */
     public static String getRootId(Uri rootUri) {
         final List<String> paths = rootUri.getPathSegments();
-        if (paths.size() < 2) {
-            throw new IllegalArgumentException("Not a root: " + rootUri);
+        if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
+            return paths.get(1);
         }
-        if (!PATH_ROOT.equals(paths.get(0))) {
-            throw new IllegalArgumentException("Not a root: " + rootUri);
-        }
-        return paths.get(1);
+        throw new IllegalArgumentException("Invalid URI: " + rootUri);
     }
 
     /**
      * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
+     *
+     * @see #isDocumentUri(Context, Uri)
      */
     public static String getDocumentId(Uri documentUri) {
         final List<String> paths = documentUri.getPathSegments();
-        if (paths.size() < 2) {
-            throw new IllegalArgumentException("Not a document: " + documentUri);
+        if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
+            return paths.get(1);
         }
-        if (!PATH_DOCUMENT.equals(paths.get(0))) {
-            throw new IllegalArgumentException("Not a document: " + documentUri);
+        if (paths.size() >= 4 && PATH_VIA.equals(paths.get(0))
+                && PATH_DOCUMENT.equals(paths.get(2))) {
+            return paths.get(3);
         }
-        return paths.get(1);
+        throw new IllegalArgumentException("Invalid URI: " + documentUri);
+    }
+
+    /**
+     * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
+     *
+     * @see #isViaUri(Uri)
+     */
+    public static String getViaDocumentId(Uri documentUri) {
+        final List<String> paths = documentUri.getPathSegments();
+        if (paths.size() >= 2 && PATH_VIA.equals(paths.get(0))) {
+            return paths.get(1);
+        }
+        throw new IllegalArgumentException("Invalid URI: " + documentUri);
     }
 
     /**
@@ -758,7 +870,6 @@
      * @param mimeType MIME type of new document
      * @param displayName name of new document
      * @return newly created document, or {@code null} if failed
-     * @hide
      */
     public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
             String mimeType, String displayName) {
@@ -778,13 +889,12 @@
     public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
             String mimeType, String displayName) throws RemoteException {
         final Bundle in = new Bundle();
-        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
+        in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
 
         final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
-        return buildDocumentUri(
-                parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
+        return out.getParcelable(DocumentsContract.EXTRA_URI);
     }
 
     /**
@@ -811,7 +921,7 @@
     public static void deleteDocument(ContentProviderClient client, Uri documentUri)
             throws RemoteException {
         final Bundle in = new Bundle();
-        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
+        in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
 
         client.call(METHOD_DELETE_DOCUMENT, null, in);
     }
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 49816f8..1a7a00f2 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -46,6 +46,7 @@
 import libcore.io.IoUtils;
 
 import java.io.FileNotFoundException;
+import java.util.Objects;
 
 /**
  * Base class for a document provider. A document provider offers read and write
@@ -125,6 +126,8 @@
     private static final int MATCH_SEARCH = 4;
     private static final int MATCH_DOCUMENT = 5;
     private static final int MATCH_CHILDREN = 6;
+    private static final int MATCH_DOCUMENT_VIA = 7;
+    private static final int MATCH_CHILDREN_VIA = 8;
 
     private String mAuthority;
 
@@ -144,6 +147,8 @@
         mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
         mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
         mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
+        mMatcher.addURI(mAuthority, "via/*/document/*", MATCH_DOCUMENT_VIA);
+        mMatcher.addURI(mAuthority, "via/*/document/*/children", MATCH_CHILDREN_VIA);
 
         // Sanity check our setup
         if (!info.exported) {
@@ -161,6 +166,35 @@
     }
 
     /**
+     * Test if a document is descendant (child, grandchild, etc) from the given
+     * parent. Providers must override this to support directory selection. You
+     * should avoid making network requests to keep this request fast.
+     *
+     * @param parentDocumentId parent to verify against.
+     * @param documentId child to verify.
+     * @return if given document is a descendant of the given parent.
+     * @see DocumentsContract.Root#FLAG_SUPPORTS_DIR_SELECTION
+     */
+    public boolean isChildDocument(String parentDocumentId, String documentId) {
+        return false;
+    }
+
+    /** {@hide} */
+    private void enforceVia(Uri documentUri) {
+        if (DocumentsContract.isViaUri(documentUri)) {
+            final String parent = DocumentsContract.getViaDocumentId(documentUri);
+            final String child = DocumentsContract.getDocumentId(documentUri);
+            if (Objects.equals(parent, child)) {
+                return;
+            }
+            if (!isChildDocument(parent, child)) {
+                throw new SecurityException(
+                        "Document " + child + " is not a descendant of " + parent);
+            }
+        }
+    }
+
+    /**
      * Create a new document and return its newly generated
      * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
      * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
@@ -182,9 +216,10 @@
 
     /**
      * Delete the requested document. Upon returning, any URI permission grants
-     * for the requested document will be revoked. If additional documents were
-     * deleted as a side effect of this call, such as documents inside a
-     * directory, the implementor is responsible for revoking those permissions.
+     * for the given document will be revoked. If additional documents were
+     * deleted as a side effect of this call (such as documents inside a
+     * directory) the implementor is responsible for revoking those permissions
+     * using {@link #revokeDocumentPermission(String)}.
      *
      * @param documentId the document to delete.
      */
@@ -420,8 +455,12 @@
                     return querySearchDocuments(
                             getRootId(uri), getSearchDocumentsQuery(uri), projection);
                 case MATCH_DOCUMENT:
+                case MATCH_DOCUMENT_VIA:
+                    enforceVia(uri);
                     return queryDocument(getDocumentId(uri), projection);
                 case MATCH_CHILDREN:
+                case MATCH_CHILDREN_VIA:
+                    enforceVia(uri);
                     if (DocumentsContract.isManageMode(uri)) {
                         return queryChildDocumentsForManage(
                                 getDocumentId(uri), projection, sortOrder);
@@ -449,6 +488,8 @@
                 case MATCH_ROOT:
                     return DocumentsContract.Root.MIME_TYPE_ITEM;
                 case MATCH_DOCUMENT:
+                case MATCH_DOCUMENT_VIA:
+                    enforceVia(uri);
                     return getDocumentType(getDocumentId(uri));
                 default:
                     return null;
@@ -460,6 +501,49 @@
     }
 
     /**
+     * Implementation is provided by the parent class. Can be overridden to
+     * provide additional functionality, but subclasses <em>must</em> always
+     * call the superclass. If the superclass returns {@code null}, the subclass
+     * may implement custom behavior.
+     * <p>
+     * This is typically used to resolve a "via" URI into a concrete document
+     * reference, issuing a narrower single-document URI permission grant along
+     * the way.
+     *
+     * @see DocumentsContract#buildDocumentViaUri(Uri, String)
+     */
+    @Override
+    public Uri canonicalize(Uri uri) {
+        final Context context = getContext();
+        switch (mMatcher.match(uri)) {
+            case MATCH_DOCUMENT_VIA:
+                enforceVia(uri);
+
+                final Uri narrowUri = DocumentsContract.buildDocumentUri(uri.getAuthority(),
+                        DocumentsContract.getDocumentId(uri));
+
+                // Caller may only have prefix grant, so extend them a grant to
+                // the narrow Uri. Caller already holds read grant to get here,
+                // so check for any other modes we should extend.
+                int modeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+                if (context.checkCallingOrSelfUriPermission(uri,
+                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+                }
+                if (context.checkCallingOrSelfUriPermission(uri,
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION
+                        | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
+                }
+                context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
+                return narrowUri;
+        }
+        return null;
+    }
+
+    /**
      * Implementation is provided by the parent class. Throws by default, and
      * cannot be overriden.
      *
@@ -496,54 +580,47 @@
      * provide additional functionality, but subclasses <em>must</em> always
      * call the superclass. If the superclass returns {@code null}, the subclass
      * may implement custom behavior.
-     *
-     * @see #openDocument(String, String, CancellationSignal)
-     * @see #deleteDocument(String)
      */
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
-        final Context context = getContext();
-
         if (!method.startsWith("android:")) {
-            // Let non-platform methods pass through
+            // Ignore non-platform methods
             return super.call(method, arg, extras);
         }
 
-        final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
-        final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
+        final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
+        final String authority = documentUri.getAuthority();
+        final String documentId = DocumentsContract.getDocumentId(documentUri);
 
-        // Require that caller can manage requested document
-        final boolean callerHasManage =
-                context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS)
-                == PackageManager.PERMISSION_GRANTED;
-        enforceWritePermissionInner(documentUri);
+        if (!mAuthority.equals(authority)) {
+            throw new SecurityException(
+                    "Requested authority " + authority + " doesn't match provider " + mAuthority);
+        }
+        enforceVia(documentUri);
 
         final Bundle out = new Bundle();
         try {
             if (METHOD_CREATE_DOCUMENT.equals(method)) {
+                enforceWritePermissionInner(documentUri);
+
                 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
                 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
 
                 final String newDocumentId = createDocument(documentId, mimeType, displayName);
-                out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
 
-                // Extend permission grant towards caller if needed
-                if (!callerHasManage) {
-                    final Uri newDocumentUri = DocumentsContract.buildDocumentUri(
-                            mAuthority, newDocumentId);
-                    context.grantUriPermission(getCallingPackage(), newDocumentUri,
-                            Intent.FLAG_GRANT_READ_URI_PERMISSION
-                            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                            | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
-                }
+                // No need to issue new grants here, since caller either has
+                // manage permission or a prefix grant. We might generate a
+                // "via" style URI if that's how they called us.
+                final Uri newDocumentUri = DocumentsContract.buildDocumentMaybeViaUri(documentUri,
+                        newDocumentId);
+                out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
 
             } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
+                enforceWritePermissionInner(documentUri);
                 deleteDocument(documentId);
 
                 // Document no longer exists, clean up any grants
-                context.revokeUriPermission(documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
-                        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                        | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+                revokeDocumentPermission(documentId);
 
             } else {
                 throw new UnsupportedOperationException("Method not supported " + method);
@@ -555,12 +632,25 @@
     }
 
     /**
+     * Revoke any active permission grants for the given
+     * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
+     * becomes invalid. Follows the same semantics as
+     * {@link Context#revokeUriPermission(Uri, int)}.
+     */
+    public final void revokeDocumentPermission(String documentId) {
+        final Context context = getContext();
+        context.revokeUriPermission(DocumentsContract.buildDocumentUri(mAuthority, documentId), ~0);
+        context.revokeUriPermission(DocumentsContract.buildViaUri(mAuthority, documentId), ~0);
+    }
+
+    /**
      * Implementation is provided by the parent class. Cannot be overriden.
      *
      * @see #openDocument(String, String, CancellationSignal)
      */
     @Override
     public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+        enforceVia(uri);
         return openDocument(getDocumentId(uri), mode, null);
     }
 
@@ -572,17 +662,47 @@
     @Override
     public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
             throws FileNotFoundException {
+        enforceVia(uri);
         return openDocument(getDocumentId(uri), mode, signal);
     }
 
     /**
      * Implementation is provided by the parent class. Cannot be overriden.
      *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
+    @Override
+    @SuppressWarnings("resource")
+    public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
+            throws FileNotFoundException {
+        enforceVia(uri);
+        final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
+        return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overriden.
+     *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
+    @Override
+    @SuppressWarnings("resource")
+    public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
+            throws FileNotFoundException {
+        enforceVia(uri);
+        final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
+        return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overriden.
+     *
      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
      */
     @Override
     public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
             throws FileNotFoundException {
+        enforceVia(uri);
         if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
             final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
             return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
@@ -600,6 +720,7 @@
     public final AssetFileDescriptor openTypedAssetFile(
             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
             throws FileNotFoundException {
+        enforceVia(uri);
         if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
             final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
             return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2ce6210..7f9f862 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5970,6 +5970,12 @@
         public static final String SHOW_PROCESSES = "show_processes";
 
         /**
+         * If 1 low power mode is enabled.
+         * @hide
+         */
+        public static final String LOW_POWER_MODE = "low_power";
+
+         /**
          * If 1, the activity manager will aggressively finish activities and
          * processes as soon as they are no longer needed.  If 0, the normal
          * extended lifetime is used.
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index 13cc88b..2cc91b9 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -130,7 +130,10 @@
     private static final String IRI
         = "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}";
 
-    private static final String HOST_NAME = IRI + "(?:\\." + IRI + ")+";
+    private static final String GOOD_GTLD_CHAR =
+        "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+    private static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}";
+    private static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
 
     public static final Pattern DOMAIN_NAME
         = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index a7ee12b..71296fa 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -161,6 +161,7 @@
                     } else if (tagName.equals(XML_MENU)) {
                         // A menu start tag denotes a submenu for an item
                         SubMenu subMenu = menuState.addSubMenuItem();
+                        registerMenu(subMenu, attrs);
 
                         // Parse the submenu into returned SubMenu
                         parseMenu(parser, attrs, subMenu);
@@ -183,9 +184,9 @@
                         if (!menuState.hasAddedItem()) {
                             if (menuState.itemActionProvider != null &&
                                     menuState.itemActionProvider.hasSubMenu()) {
-                                menuState.addSubMenuItem();
+                                registerMenu(menuState.addSubMenuItem(), attrs);
                             } else {
-                                menuState.addItem();
+                                registerMenu(menuState.addItem(), attrs);
                             }
                         }
                     } else if (tagName.equals(XML_MENU)) {
@@ -200,7 +201,30 @@
             eventType = parser.next();
         }
     }
-    
+
+    /**
+     * The method is a hook for layoutlib to do its magic.
+     * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+     * appears to do nothing.
+     */
+    private void registerMenu(@SuppressWarnings("unused") MenuItem item,
+            @SuppressWarnings("unused") AttributeSet set) {
+    }
+
+    /**
+     * The method is a hook for layoutlib to do its magic.
+     * Nothing is needed outside of LayoutLib. However, it should not be deleted because it
+     * appears to do nothing.
+     */
+    private void registerMenu(@SuppressWarnings("unused") SubMenu subMenu,
+            @SuppressWarnings("unused") AttributeSet set) {
+    }
+
+    // Needed by layoutlib.
+    /*package*/ Context getContext() {
+        return mContext;
+    }
+
     private static class InflatedOnMenuItemClickListener
             implements MenuItem.OnMenuItemClickListener {
         private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
@@ -446,9 +470,11 @@
             }
         }
 
-        public void addItem() {
+        public MenuItem addItem() {
             itemAdded = true;
-            setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
+            MenuItem item = menu.add(groupId, itemId, itemCategoryOrder, itemTitle);
+            setItem(item);
+            return item;
         }
         
         public SubMenu addSubMenuItem() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 097dff0..374787e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5821,6 +5821,13 @@
         }
     }
 
+    public void dispatchUnhandledInputEvent(InputEvent event) {
+        if (event instanceof KeyEvent) {
+            dispatchUnhandledKey((KeyEvent) event);
+            return;
+        }
+    }
+
     public void dispatchAppVisibility(boolean visible) {
         Message msg = mHandler.obtainMessage(MSG_DISPATCH_APP_VISIBILITY);
         msg.arg1 = visible ? 1 : 0;
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 438a9da..225cd6d 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -29,6 +29,8 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import com.android.internal.R;
+
 public abstract class AbsSeekBar extends ProgressBar {
     private Drawable mThumb;
     private int mThumbOffset;
@@ -289,28 +291,39 @@
      */
     private void setThumbPos(int w, Drawable thumb, float scale, int gap) {
         int available = w - mPaddingLeft - mPaddingRight;
-        int thumbWidth = thumb.getIntrinsicWidth();
-        int thumbHeight = thumb.getIntrinsicHeight();
+        final int thumbWidth = thumb.getIntrinsicWidth();
+        final int thumbHeight = thumb.getIntrinsicHeight();
         available -= thumbWidth;
 
         // The extra space for the thumb to move on the track
         available += mThumbOffset * 2;
 
-        int thumbPos = (int) (scale * available + 0.5f);
+        final int thumbPos = (int) (scale * available + 0.5f);
 
-        int topBound, bottomBound;
+        final int top, bottom;
         if (gap == Integer.MIN_VALUE) {
-            Rect oldBounds = thumb.getBounds();
-            topBound = oldBounds.top;
-            bottomBound = oldBounds.bottom;
+            final Rect oldBounds = thumb.getBounds();
+            top = oldBounds.top;
+            bottom = oldBounds.bottom;
         } else {
-            topBound = gap;
-            bottomBound = gap + thumbHeight;
+            top = gap;
+            bottom = gap + thumbHeight;
         }
-        
-        // Canvas will be translated, so 0,0 is where we start drawing
+
         final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
-        thumb.setBounds(left, topBound, left + thumbWidth, bottomBound);
+        final int right = left + thumbWidth;
+
+        final Drawable background = getBackground();
+        if (background != null && background.supportsHotspots()) {
+            final Rect bounds = mThumb.getBounds();
+            final int offsetX = mPaddingLeft - mThumbOffset;
+            final int offsetY = mPaddingTop;
+            background.setHotspotBounds(left + offsetX, bounds.top + offsetY,
+                    right + offsetX, bounds.bottom + offsetY);
+        }
+
+        // Canvas will be translated, so 0,0 is where we start drawing
+        thumb.setBounds(left, top, right, bottom);
     }
 
     /**
@@ -328,6 +341,7 @@
     @Override
     protected synchronized void onDraw(Canvas canvas) {
         super.onDraw(canvas);
+
         if (mThumb != null) {
             canvas.save();
             // Translate the padding. For the x, we need to allow the thumb to
@@ -424,10 +438,24 @@
         return true;
     }
 
+    private void setHotspot(int id, float x, float y) {
+        final Drawable bg = getBackground();
+        if (bg != null && bg.supportsHotspots()) {
+            bg.setHotspot(id, x, y);
+        }
+    }
+
+    private void clearHotspot(int id) {
+        final Drawable bg = getBackground();
+        if (bg != null && bg.supportsHotspots()) {
+            bg.removeHotspot(id);
+        }
+    }
+
     private void trackTouchEvent(MotionEvent event) {
         final int width = getWidth();
         final int available = width - mPaddingLeft - mPaddingRight;
-        int x = (int)event.getX();
+        final int x = (int) event.getX();
         float scale;
         float progress = 0;
         if (isLayoutRtl() && mMirrorForRtl) {
@@ -451,7 +479,8 @@
         }
         final int max = getMax();
         progress += scale * max;
-        
+
+        setHotspot(R.attr.state_pressed, x, (int) event.getY());
         setProgress((int) progress, true);
     }
 
@@ -477,6 +506,7 @@
      * canceled.
      */
     void onStopTrackingTouch() {
+        clearHotspot(R.attr.state_pressed);
         mIsDragging = false;
     }
 
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 4298545..9e17cca 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -261,15 +261,13 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
         final Drawable buttonDrawable = mButtonDrawable;
         if (buttonDrawable != null) {
             final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
             final int drawableHeight = buttonDrawable.getIntrinsicHeight();
             final int drawableWidth = buttonDrawable.getIntrinsicWidth();
 
-            int top = 0;
+            final int top;
             switch (verticalGravity) {
                 case Gravity.BOTTOM:
                     top = getHeight() - drawableHeight;
@@ -277,12 +275,24 @@
                 case Gravity.CENTER_VERTICAL:
                     top = (getHeight() - drawableHeight) / 2;
                     break;
+                default:
+                    top = 0;
             }
-            int bottom = top + drawableHeight;
-            int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
-            int right = isLayoutRtl() ? getWidth() : drawableWidth;
+            final int bottom = top + drawableHeight;
+            final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
+            final int right = isLayoutRtl() ? getWidth() : drawableWidth;
 
             buttonDrawable.setBounds(left, top, right, bottom);
+
+            final Drawable background = getBackground();
+            if (background != null && background.supportsHotspots()) {
+                background.setHotspotBounds(left, top, right, bottom);
+            }
+        }
+
+        super.onDraw(canvas);
+
+        if (buttonDrawable != null) {
             buttonDrawable.draw(canvas);
         }
     }
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 3d23e4d..08af4de 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -773,8 +773,6 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
         final Rect tempRect = mTempRect;
         final Drawable trackDrawable = mTrackDrawable;
         final Drawable thumbDrawable = mThumbDrawable;
@@ -785,16 +783,12 @@
         final int switchRight = mSwitchRight;
         final int switchBottom = mSwitchBottom;
         trackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
-        trackDrawable.draw(canvas);
-
-        final int saveCount = canvas.save();
-
         trackDrawable.getPadding(tempRect);
+
         final int switchInnerLeft = switchLeft + tempRect.left;
         final int switchInnerTop = switchTop + tempRect.top;
         final int switchInnerRight = switchRight - tempRect.right;
         final int switchInnerBottom = switchBottom - tempRect.bottom;
-        canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
 
         // Relies on mTempRect, MUST be called first!
         final int thumbPos = getThumbOffset();
@@ -803,6 +797,18 @@
         int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
         int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
         thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+
+        final Drawable background = getBackground();
+        if (background != null && background.supportsHotspots()) {
+            background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+        }
+
+        super.onDraw(canvas);
+
+        trackDrawable.draw(canvas);
+
+        final int saveCount = canvas.save();
+        canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
         thumbDrawable.draw(canvas);
 
         final int drawableState[] = getDrawableState();
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index b776226..5d7d322 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -392,8 +392,8 @@
     private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
         final int ordering = getOrdering(categoryOrder);
         
-        final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
-                ordering, title, mDefaultShowAsAction);
+        final MenuItemImpl item = createNewMenuItem(group, id, categoryOrder, ordering, title,
+                mDefaultShowAsAction);
 
         if (mCurrentMenuInfo != null) {
             // Pass along the current menu info
@@ -405,7 +405,14 @@
         
         return item;
     }
-    
+
+    // Layoutlib overrides this method to return its custom implementation of MenuItemImpl
+    private MenuItemImpl createNewMenuItem(int group, int id, int categoryOrder, int ordering,
+            CharSequence title, int defaultShowAsAction) {
+        return new MenuItemImpl(this, group, id, categoryOrder, ordering, title,
+                defaultShowAsAction);
+    }
+
     public MenuItem add(CharSequence title) {
         return addInternal(0, 0, 0, title);
     }
diff --git a/core/res/res/drawable/btn_radio_quantum.xml b/core/res/res/drawable/btn_radio_quantum.xml
index 152e3d3..0f9ebce 100644
--- a/core/res/res/drawable/btn_radio_quantum.xml
+++ b/core/res/res/drawable/btn_radio_quantum.xml
@@ -15,18 +15,20 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:state_enabled="true" android:state_pressed="true">
-        <bitmap android:src="@drawable/btn_radio_on_pressed_qntm_alpha"
-            android:tint="?attr/colorControlActivated" />
+    <item android:state_enabled="false" android:state_checked="true">
+        <bitmap android:src="@drawable/btn_radio_on_qntm_alpha"
+            android:tint="?attr/colorControlNormal"
+            android:alpha="?attr/disabledAlpha" />
+    </item>
+    <item android:state_enabled="false">
+        <bitmap android:src="@drawable/btn_radio_off_qntm_alpha"
+            android:tint="?attr/colorControlNormal"
+            android:alpha="?attr/disabledAlpha" />
     </item>
     <item android:state_checked="true">
         <bitmap android:src="@drawable/btn_radio_on_qntm_alpha"
             android:tint="?attr/colorControlActivated" />
     </item>
-    <item android:state_enabled="true" android:state_pressed="true">
-        <bitmap android:src="@drawable/btn_radio_off_pressed_qntm_alpha"
-            android:tint="?attr/colorControlActivated" />
-    </item>
     <item>
         <bitmap android:src="@drawable/btn_radio_off_qntm_alpha"
             android:tint="?attr/colorControlNormal" />
diff --git a/core/res/res/drawable/scrubber_control_selector_quantum.xml b/core/res/res/drawable/scrubber_control_selector_quantum.xml
index e34f64a..ef3af0c 100644
--- a/core/res/res/drawable/scrubber_control_selector_quantum.xml
+++ b/core/res/res/drawable/scrubber_control_selector_quantum.xml
@@ -19,10 +19,6 @@
         <bitmap android:src="@drawable/scrubber_control_off_qntm_alpha"
             android:tint="?attr/colorControlNormal" />
     </item>
-    <item android:state_pressed="true">
-        <bitmap android:src="@drawable/scrubber_control_on_pressed_qntm_alpha"
-            android:tint="?attr/colorControlActivated" />
-    </item>
     <item>
         <bitmap android:src="@drawable/scrubber_control_on_qntm_alpha"
             android:tint="?attr/colorControlActivated" />
diff --git a/core/res/res/drawable/switch_inner_quantum.xml b/core/res/res/drawable/switch_inner_quantum.xml
index 915377e..856895e 100644
--- a/core/res/res/drawable/switch_inner_quantum.xml
+++ b/core/res/res/drawable/switch_inner_quantum.xml
@@ -15,18 +15,20 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:state_pressed="true">
+    <item android:state_enabled="false" android:state_checked="true">
         <nine-patch android:src="@drawable/switch_on_qntm_alpha"
-            android:tint="?attr/colorControlActivated" />
+            android:tint="?attr/colorControlNormal"
+            android:alpha="?attr/disabledAlpha" />
+    </item>
+    <item android:state_enabled="false">
+        <nine-patch android:src="@drawable/switch_off_qntm_alpha"
+            android:tint="?attr/colorControlNormal"
+            android:alpha="?attr/disabledAlpha" />
     </item>
     <item android:state_checked="true">
         <nine-patch android:src="@drawable/switch_on_qntm_alpha"
             android:tint="?attr/colorControlActivated" />
     </item>
-    <item android:state_pressed="true">
-        <nine-patch android:src="@drawable/switch_off_qntm_alpha"
-            android:tint="?attr/colorControlActivated" />
-    </item>
     <item>
         <nine-patch android:src="@drawable/switch_off_qntm_alpha"
             android:tint="?attr/colorControlNormal" />
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index bdc7ad0..0cbb655 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -485,11 +485,18 @@
     </style>
 
     <style name="Widget.Quantum.CompoundButton" parent="Widget.CompoundButton"/>
-    <style name="Widget.Quantum.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox"/>
-    <style name="Widget.Quantum.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton"/>
+
+    <style name="Widget.Quantum.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox">
+        <item name="background">?attr/selectableItemBackground</item>
+    </style>
+
+    <style name="Widget.Quantum.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton">
+        <item name="background">?attr/selectableItemBackground</item>
+    </style>
 
     <style name="Widget.Quantum.CompoundButton.Star" parent="Widget.CompoundButton.Star">
         <item name="button">@drawable/btn_star_quantum</item>
+        <item name="background">?attr/selectableItemBackground</item>
     </style>
 
     <style name="Widget.Quantum.CompoundButton.Switch">
@@ -501,6 +508,7 @@
         <item name="thumbTextPadding">12dip</item>
         <item name="switchMinWidth">72dip</item>
         <item name="switchPadding">16dip</item>
+        <item name="background">?attr/selectableItemBackground</item>
     </style>
 
     <style name="Widget.Quantum.EditText" parent="Widget.EditText"/>
@@ -619,6 +627,7 @@
         <item name="paddingStart">16dip</item>
         <item name="paddingEnd">16dip</item>
         <item name="mirrorForRtl">true</item>
+        <item name="background">?attr/selectableItemBackground</item>
     </style>
 
     <style name="Widget.Quantum.RatingBar" parent="Widget.RatingBar">
@@ -846,7 +855,15 @@
         <item name="popupBackground">?attr/colorBackground</item>
     </style>
 
+    <style name="Widget.Quantum.Light.CompoundButton" parent="Widget.Quantum.CompoundButton"/>
     <style name="Widget.Quantum.Light.CompoundButton.CheckBox" parent="Widget.Quantum.CompoundButton.CheckBox"/>
+    <style name="Widget.Quantum.Light.CompoundButton.RadioButton" parent="Widget.Quantum.CompoundButton.RadioButton"/>
+    <style name="Widget.Quantum.Light.CompoundButton.Star" parent="Widget.Quantum.CompoundButton.Star"/>
+
+    <style name="Widget.Quantum.Light.CompoundButton.Switch" parent="Widget.Quantum.CompoundButton.Switch">
+        <item name="switchTextAppearance">@style/TextAppearance.Quantum.Light.Widget.Switch</item>
+    </style>
+
     <style name="Widget.Quantum.Light.ListView.DropDown" parent="Widget.Quantum.ListView.DropDown"/>
     <style name="Widget.Quantum.Light.EditText" parent="Widget.Quantum.EditText"/>
     <style name="Widget.Quantum.Light.ExpandableListView" parent="Widget.Quantum.ExpandableListView"/>
@@ -916,7 +933,6 @@
         <item name="maxHeight">16dip</item>
     </style>
 
-    <style name="Widget.Quantum.Light.CompoundButton.RadioButton" parent="Widget.Quantum.CompoundButton.RadioButton"/>
     <style name="Widget.Quantum.Light.ScrollView" parent="Widget.Quantum.ScrollView"/>
     <style name="Widget.Quantum.Light.HorizontalScrollView" parent="Widget.Quantum.HorizontalScrollView"/>
 
@@ -932,7 +948,6 @@
 
     <style name="Widget.Quantum.Light.Spinner.DropDown" parent="Widget.Quantum.Spinner.DropDown"/>
     <style name="Widget.Quantum.Light.Spinner.DropDown.ActionBar" parent="Widget.Quantum.Spinner.DropDown.ActionBar"/>
-    <style name="Widget.Quantum.Light.CompoundButton.Star" parent="Widget.Quantum.CompoundButton.Star"/>
     <style name="Widget.Quantum.Light.TabWidget" parent="Widget.Quantum.TabWidget"/>
     <style name="Widget.Quantum.Light.WebTextView" parent="Widget.Quantum.WebTextView"/>
     <style name="Widget.Quantum.Light.WebView" parent="Widget.Quantum.WebView"/>
@@ -989,10 +1004,6 @@
         <item name="backgroundSplit">?attr/colorPrimary</item>
     </style>
 
-    <style name="Widget.Quantum.Light.CompoundButton.Switch" parent="Widget.Quantum.CompoundButton.Switch">
-        <item name="switchTextAppearance">@style/TextAppearance.Quantum.Light.Widget.Switch</item>
-    </style>
-
     <style name="Widget.Quantum.Light.FastScroll" parent="Widget.Quantum.FastScroll"/>
 
     <style name="Widget.Quantum.Light.MediaRouteButton" parent="Widget.Quantum.MediaRouteButton">
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index 6fb8946..cd45017 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -19,12 +19,14 @@
 import android.content.ContentUris;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
 import java.io.File;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
-import junit.framework.TestCase;
 
 public class UriTest extends TestCase {
 
@@ -752,4 +754,54 @@
         assertEquals("d e", Uri.parse("http://a/b?c=d%20e").getQueryParameter("c"));
         assertEquals("d e", Uri.parse("http://a/b?c=d+e").getQueryParameter("c"));
     }
+
+    public void testPathPrefixMatch() {
+        // Exact match
+        assertTrue(Uri.parse("content://com.example/path").isPathPrefixMatch(
+                Uri.parse("content://com.example/path/")));
+        assertTrue(Uri.parse("content://com.example/path").isPathPrefixMatch(
+                Uri.parse("content://com.example/path")));
+        assertTrue(Uri.parse("content://com.example///path///").isPathPrefixMatch(
+                Uri.parse("content://com.example/path/")));
+        assertTrue(Uri.parse("content://com.example/path").isPathPrefixMatch(
+                Uri.parse("content://com.example///path///")));
+
+        // Child match
+        assertTrue(Uri.parse("content://com.example/path/to/child").isPathPrefixMatch(
+                Uri.parse("content://com.example/path/")));
+        assertTrue(Uri.parse("content://com.example/path/to/child").isPathPrefixMatch(
+                Uri.parse("content://com.example/path")));
+
+        // Extra parameters
+        assertTrue(Uri.parse("content://com.example/path#fragment").isPathPrefixMatch(
+                Uri.parse("content://com.example/path/")));
+        assertTrue(Uri.parse("content://com.example/path?q=v").isPathPrefixMatch(
+                Uri.parse("content://com.example/path/")));
+        assertTrue(Uri.parse("content://com.example/path/?q=v").isPathPrefixMatch(
+                Uri.parse("content://com.example/path/")));
+
+        // Different path
+        assertFalse(Uri.parse("content://com.example/path").isPathPrefixMatch(
+                Uri.parse("content://com.example/path/deeper/")));
+        assertFalse(Uri.parse("content://com.example/path2").isPathPrefixMatch(
+                Uri.parse("content://com.example/path")));
+
+        // Top-level match
+        assertTrue(Uri.parse("content://com.example/path/").isPathPrefixMatch(
+                Uri.parse("content://com.example/")));
+        assertTrue(Uri.parse("content://com.example/path/").isPathPrefixMatch(
+                Uri.parse("content://com.example")));
+
+        // Different prefixes
+        assertFalse(Uri.parse("content://com.example/path/").isPathPrefixMatch(
+                Uri.parse("file://com.example/path/")));
+        assertFalse(Uri.parse("content://com.example/path/").isPathPrefixMatch(
+                Uri.parse("content://org.example/path/")));
+
+        // Escaping
+        assertTrue(Uri.parse("content://com.example/path path/").isPathPrefixMatch(
+                Uri.parse("content://com.example/path%20path/")));
+        assertFalse(Uri.parse("content://com.example/path/path").isPathPrefixMatch(
+                Uri.parse("content://com.example/path%2Fpath")));
+    }
 }
diff --git a/docs/html/guide/topics/ui/actionbar.jd b/docs/html/guide/topics/ui/actionbar.jd
index f169f5f..a61696a 100644
--- a/docs/html/guide/topics/ui/actionbar.jd
+++ b/docs/html/guide/topics/ui/actionbar.jd
@@ -1126,8 +1126,8 @@
 uses a string array as the data source:</p>
 
 <pre>
-SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.action_list,
-          android.R.layout.simple_spinner_dropdown_item);
+SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this,
+        R.array.action_list, android.R.layout.simple_spinner_dropdown_item);
 </pre>
 
 <p>The {@link android.widget.ArrayAdapter#createFromResource createFromResource()} method takes
@@ -1179,10 +1179,13 @@
   public boolean onNavigationItemSelected(int position, long itemId) {
     // Create new fragment from our own Fragment class
     ListContentFragment newFragment = new ListContentFragment();
-    FragmentTransaction ft = openFragmentTransaction();
+    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+
     // Replace whatever is in the fragment container with this fragment
-    //  and give the fragment a tag name equal to the string at the position selected
+    // and give the fragment a tag name equal to the string at the position
+    // selected
     ft.replace(R.id.fragment_container, newFragment, strings[position]);
+
     // Apply changes
     ft.commit();
     return true;
@@ -1210,7 +1213,8 @@
     &#64;Override
     public void onAttach(Activity activity) {
       // This is the first callback received; here we can set the text for
-      // the fragment as defined by the tag specified during the fragment transaction
+      // the fragment as defined by the tag specified during the fragment
+      // transaction
       super.onAttach(activity);
       mText = getTag();
     }
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 21cd5db..74d1219 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -518,9 +518,16 @@
     public void removeHotspot(int key) {}
 
     /**
-     * Removes all hotspots from the drawable.
+     * Immediately removes all hotspots from the drawable.
      */
     public void clearHotspots() {}
+    
+    /**
+     * Sets the bounds to which hotspots are constrained.
+     *
+     * @hide until we finalize these APIs
+     */
+    public void setHotspotBounds(int left, int top, int right, int bottom) {}
 
     /**
      * Whether this drawable requests projection.
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index b366987..639d719 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -290,6 +290,26 @@
 
         return false;
     }
+    
+    /**
+     * @hide
+     */
+    @Override
+    public boolean isProjected() {
+        if (super.isProjected()) {
+            return true;
+        }
+
+        final ChildDrawable[] layers = mLayerState.mChildren;
+        final int N = mLayerState.mNum;
+        for (int i = 0; i < N; i++) {
+            if (layers[i].mDrawable.isProjected()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 
     /**
      * Add a new layer to this drawable. The new layer is identified by an id.
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 796da50..e3f57e9 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -139,8 +139,8 @@
     public boolean draw(Canvas c, Paint p) {
         final Rect bounds = mBounds;
         final Rect padding = mPadding;
-        final float dX = Math.max(mX, bounds.right - mX);
-        final float dY = Math.max(mY, bounds.bottom - mY);
+        final float dX = Math.max(mX - bounds.left, bounds.right - mX);
+        final float dY = Math.max(mY - bounds.top, bounds.bottom - mY);
         final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY));
 
         final float enterState = mEnterState;
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
index 813d755c..5101e35 100644
--- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -25,12 +25,13 @@
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Ripple.RippleAnimator;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.graphics.drawable.Ripple.RippleAnimator;
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.R;
@@ -45,6 +46,7 @@
  * Documentation pending.
  */
 public class TouchFeedbackDrawable extends LayerDrawable {
+    private static final String LOG_TAG = TouchFeedbackDrawable.class.getSimpleName();
     private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
 
     /** The maximum number of ripples supported. */
@@ -53,6 +55,9 @@
     private final Rect mTempRect = new Rect();
     private final Rect mPaddingRect = new Rect();
 
+    /** Current ripple effect bounds, used to constrain ripple effects. */
+    private final Rect mHotspotBounds = new Rect();
+
     /** Current drawing bounds, used to compute dirty region. */
     private final Rect mDrawingBounds = new Rect();
 
@@ -83,6 +88,9 @@
     /** Whether the animation runnable has been posted. */
     private boolean mAnimating;
 
+    /** Whether bounds are being overridden. */
+    private boolean mOverrideBounds;
+
     TouchFeedbackDrawable() {
         this(new TouchFeedbackState(null, null, null), null, null);
     }
@@ -114,9 +122,22 @@
     @Override
     protected void onBoundsChange(Rect bounds) {
         super.onBoundsChange(bounds);
+        
+        if (!mOverrideBounds) {
+            mHotspotBounds.set(bounds);
+        }
 
+        onHotspotBoundsChange();
+    }
+
+    private void onHotspotBoundsChange() {
+        final int x = mHotspotBounds.centerX();
+        final int y = mHotspotBounds.centerY();
         final int N = mActiveRipplesCount;
         for (int i = 0; i < N; i++) {
+            if (mState.mPinned) {
+                mActiveRipples[i].move(x, y);
+            }
             mActiveRipples[i].onBoundsChanged();
         }
     }
@@ -289,13 +310,18 @@
             mTouchedRipples = new SparseArray<Ripple>();
             mActiveRipples = new Ripple[MAX_RIPPLES];
         }
+        
+        if (mActiveRipplesCount >= MAX_RIPPLES) {
+            Log.e(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
+            return;
+        }
 
         final Ripple ripple = mTouchedRipples.get(id);
         if (ripple == null) {
-            final Rect bounds = getBounds();
             final Rect padding = mPaddingRect;
             getPadding(padding);
 
+            final Rect bounds = mHotspotBounds;
             if (mState.mPinned) {
                 x = bounds.exactCenterX();
                 y = bounds.exactCenterY();
@@ -308,7 +334,12 @@
 
             mActiveRipples[mActiveRipplesCount++] = newRipple;
             mTouchedRipples.put(id, newRipple);
-        } else if (!mState.mPinned) {
+        } else if (mState.mPinned) {
+            final Rect bounds = mHotspotBounds;
+            x = bounds.exactCenterX();
+            y = bounds.exactCenterY();
+            ripple.move(x, y);
+        } else {
             ripple.move(x, y);
         }
 
@@ -338,6 +369,7 @@
 
         final int n = mTouchedRipples.size();
         for (int i = 0; i < n; i++) {
+            // TODO: Use a fast exit, maybe just fade out?
             mTouchedRipples.valueAt(i).animate().exit();
         }
 
@@ -348,6 +380,16 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    public void setHotspotBounds(int left, int top, int right, int bottom) {
+        mOverrideBounds = true;
+        mHotspotBounds.set(left, top, right, bottom);
+        onHotspotBoundsChange();
+    }
+
+    /**
      * Schedules the next animation, if necessary.
      */
     private void scheduleAnimation() {
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index 5187f8f..d22cb8a 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -42,6 +42,7 @@
 
 Font::Font(FontRenderer* state, const Font::FontDescription& desc) :
         mState(state), mDescription(desc) {
+    mDeviceProperties = SkDeviceProperties::Make(SkDeviceProperties::Geometry::MakeDefault(), 1.0f);
 }
 
 Font::FontDescription::FontDescription(const SkPaint* paint, const mat4& matrix) {
@@ -272,7 +273,7 @@
     if (cachedGlyph) {
         // Is the glyph still in texture cache?
         if (!cachedGlyph->mIsValid) {
-            SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform);
+            SkAutoGlyphCache autoCache(*paint, &mDeviceProperties, &mDescription.mLookupTransform);
             const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), textUnit);
             updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), cachedGlyph, precaching);
         }
@@ -464,7 +465,7 @@
     CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
     mCachedGlyphs.add(glyph, newGlyph);
 
-    SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform);
+    SkAutoGlyphCache autoCache(*paint, &mDeviceProperties, &mDescription.mLookupTransform);
     const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), glyph);
     newGlyph->mIsValid = false;
     newGlyph->mGlyphIndex = skiaGlyph.fID;
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 02197bc..6ddd4f2 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -19,6 +19,8 @@
 
 #include <utils/KeyedVector.h>
 
+#include <SkScalar.h>
+#include <SkDeviceProperties.h>
 #include <SkGlyphCache.h>
 #include <SkScalerContext.h>
 #include <SkPaint.h>
@@ -146,6 +148,7 @@
     DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
 
     bool mIdentityTransform;
+    SkDeviceProperties mDeviceProperties;
 };
 
 inline int strictly_order_type(const Font::FontDescription& lhs,
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index edfa36a..45df065 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -309,7 +309,7 @@
                 });
 
         // rational (n) -- in particular rational x 9
-        checkKeyGetAndSetArray("android.sensor.calibrationTransform", Rational[].class,
+        checkKeyGetAndSetArray("android.sensor.calibrationTransform1", Rational[].class,
                 new Rational[] {
                         new Rational(1, 2), new Rational(3, 4), new Rational(5, 6),
                         new Rational(7, 8), new Rational(9, 10), new Rational(10, 11),
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 6b77a7c..159ee66 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -9,18 +9,17 @@
         android:label="@string/app_label"
         android:supportsRtl="true">
 
-        <!-- TODO: allow rotation when state saving is in better shape -->
         <activity
             android:name=".DocumentsActivity"
             android:theme="@style/Theme"
             android:icon="@drawable/ic_doc_text">
-            <intent-filter android:priority="100">
+            <intent-filter>
                 <action android:name="android.intent.action.OPEN_DOCUMENT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.OPENABLE" />
                 <data android:mimeType="*/*" />
             </intent-filter>
-            <intent-filter android:priority="100">
+            <intent-filter>
                 <action android:name="android.intent.action.CREATE_DOCUMENT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.OPENABLE" />
@@ -33,6 +32,10 @@
                 <data android:mimeType="*/*" />
             </intent-filter>
             <intent-filter>
+                <action android:name="android.intent.action.PICK_DIRECTORY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
                 <action android:name="android.provider.action.MANAGE_ROOT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="vnd.android.document/root" />
@@ -57,14 +60,5 @@
                 <data android:scheme="package" />
             </intent-filter>
         </receiver>
-
-        <!-- TODO: remove when we have real clients -->
-        <activity android:name=".TestActivity" android:enabled="false">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
     </application>
 </manifest>
diff --git a/packages/DocumentsUI/res/layout/fragment_pick.xml b/packages/DocumentsUI/res/layout/fragment_pick.xml
new file mode 100644
index 0000000..4a2fd03
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/fragment_pick.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <!-- Le sigh, this really should be an asset -->
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="#ccc" />
+
+    <LinearLayout
+        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">
+
+        <Button
+            android:id="@android:id/button1"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="?android:attr/selectableItemBackground"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textAllCaps="false"
+            android:padding="8dp" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 92c30ba..c1a9d72 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -44,6 +44,8 @@
     <string name="menu_share">Share</string>
     <!-- Menu item title that deletes the selected documents [CHAR LIMIT=24] -->
     <string name="menu_delete">Delete</string>
+    <!-- Menu item title that selects the current directory [CHAR LIMIT=48] -->
+    <string name="menu_select">Select \"<xliff:g id="directory" example="My Directory">^1</xliff:g>\"</string>
 
     <!-- Action mode title summarizing the number of documents selected [CHAR LIMIT=32] -->
     <string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 4212e96..9f76991 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -24,6 +24,7 @@
 import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT;
 import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
 import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN;
+import static com.android.documentsui.DocumentsActivity.State.ACTION_PICK_DIRECTORY;
 import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
 import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
 
@@ -202,6 +203,8 @@
             final String mimeType = getIntent().getType();
             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
             SaveFragment.show(getFragmentManager(), mimeType, title);
+        } else if (mState.action == ACTION_PICK_DIRECTORY) {
+            PickFragment.show(getFragmentManager());
         }
 
         if (mState.action == ACTION_GET_CONTENT) {
@@ -209,7 +212,8 @@
             moreApps.setComponent(null);
             moreApps.setPackage(null);
             RootsFragment.show(getFragmentManager(), moreApps);
-        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE) {
+        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE
+                || mState.action == ACTION_PICK_DIRECTORY) {
             RootsFragment.show(getFragmentManager(), null);
         }
 
@@ -236,6 +240,8 @@
             mState.action = ACTION_CREATE;
         } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
             mState.action = ACTION_GET_CONTENT;
+        } else if (Intent.ACTION_PICK_DIRECTORY.equals(action)) {
+            mState.action = ACTION_PICK_DIRECTORY;
         } else if (DocumentsContract.ACTION_MANAGE_ROOT.equals(action)) {
             mState.action = ACTION_MANAGE;
         }
@@ -434,7 +440,8 @@
             actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
             actionBar.setIcon(new ColorDrawable());
 
-            if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
+            if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT
+                    || mState.action == ACTION_PICK_DIRECTORY) {
                 actionBar.setTitle(R.string.title_open);
             } else if (mState.action == ACTION_CREATE) {
                 actionBar.setTitle(R.string.title_save);
@@ -576,7 +583,7 @@
         sortSize.setVisible(mState.showSize);
 
         final boolean searchVisible;
-        if (mState.action == ACTION_CREATE) {
+        if (mState.action == ACTION_CREATE || mState.action == ACTION_PICK_DIRECTORY) {
             createDir.setVisible(cwd != null && cwd.isCreateSupported());
             searchVisible = false;
 
@@ -586,7 +593,9 @@
                 list.setVisible(false);
             }
 
-            SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
+            if (mState.action == ACTION_CREATE) {
+                SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
+            }
         } else {
             createDir.setVisible(false);
 
@@ -819,7 +828,7 @@
 
         if (cwd == null) {
             // No directory means recents
-            if (mState.action == ACTION_CREATE) {
+            if (mState.action == ACTION_CREATE || mState.action == ACTION_PICK_DIRECTORY) {
                 RecentsCreateFragment.show(fm);
             } else {
                 DirectoryFragment.showRecentsOpen(fm, anim);
@@ -848,6 +857,15 @@
             }
         }
 
+        if (mState.action == ACTION_PICK_DIRECTORY) {
+            final PickFragment pick = PickFragment.get(fm);
+            if (pick != null) {
+                final CharSequence displayName = (mState.stack.size() <= 1) ? root.title
+                        : cwd.displayName;
+                pick.setPickTarget(cwd, displayName);
+            }
+        }
+
         final RootsFragment roots = RootsFragment.get(fm);
         if (roots != null) {
             roots.onCurrentRootChanged();
@@ -1002,12 +1020,18 @@
         new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor());
     }
 
+    public void onPickRequested(DocumentInfo pickTarget) {
+        final Uri viaUri = DocumentsContract.buildViaUri(pickTarget.authority,
+                pickTarget.documentId);
+        new PickFinishTask(viaUri).executeOnExecutor(getCurrentExecutor());
+    }
+
     private void saveStackBlocking() {
         final ContentResolver resolver = getContentResolver();
         final ContentValues values = new ContentValues();
 
         final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
-        if (mState.action == ACTION_CREATE) {
+        if (mState.action == ACTION_CREATE || mState.action == ACTION_PICK_DIRECTORY) {
             // Remember stack for last create
             values.clear();
             values.put(RecentColumns.KEY, mState.stack.buildKey());
@@ -1040,6 +1064,11 @@
 
         if (mState.action == ACTION_GET_CONTENT) {
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } else if (mState.action == ACTION_PICK_DIRECTORY) {
+            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
         } else {
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@@ -1121,6 +1150,25 @@
         }
     }
 
+    private class PickFinishTask extends AsyncTask<Void, Void, Void> {
+        private final Uri mUri;
+
+        public PickFinishTask(Uri uri) {
+            mUri = uri;
+        }
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            saveStackBlocking();
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void result) {
+            onFinished(mUri);
+        }
+    }
+
     public static class State implements android.os.Parcelable {
         public int action;
         public String[] acceptMimes;
@@ -1154,7 +1202,8 @@
         public static final int ACTION_OPEN = 1;
         public static final int ACTION_CREATE = 2;
         public static final int ACTION_GET_CONTENT = 3;
-        public static final int ACTION_MANAGE = 4;
+        public static final int ACTION_PICK_DIRECTORY = 4;
+        public static final int ACTION_MANAGE = 5;
 
         public static final int MODE_UNKNOWN = 0;
         public static final int MODE_LIST = 1;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
new file mode 100644
index 0000000..a9e488a1
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 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.documentsui;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.android.documentsui.model.DocumentInfo;
+
+import java.util.Locale;
+
+/**
+ * Display pick confirmation bar, usually for selecting a directory.
+ */
+public class PickFragment extends Fragment {
+    public static final String TAG = "PickFragment";
+
+    private DocumentInfo mPickTarget;
+
+    private View mContainer;
+    private Button mPick;
+
+    public static void show(FragmentManager fm) {
+        final PickFragment fragment = new PickFragment();
+
+        final FragmentTransaction ft = fm.beginTransaction();
+        ft.replace(R.id.container_save, fragment, TAG);
+        ft.commitAllowingStateLoss();
+    }
+
+    public static PickFragment get(FragmentManager fm) {
+        return (PickFragment) fm.findFragmentByTag(TAG);
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        mContainer = inflater.inflate(R.layout.fragment_pick, container, false);
+
+        mPick = (Button) mContainer.findViewById(android.R.id.button1);
+        mPick.setOnClickListener(mPickListener);
+
+        setPickTarget(null, null);
+
+        return mContainer;
+    }
+
+    private View.OnClickListener mPickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final DocumentsActivity activity = DocumentsActivity.get(PickFragment.this);
+            activity.onPickRequested(mPickTarget);
+        }
+    };
+
+    public void setPickTarget(DocumentInfo pickTarget, CharSequence displayName) {
+        mPickTarget = pickTarget;
+
+        if (mPickTarget != null) {
+            mContainer.setVisibility(View.VISIBLE);
+            final Locale locale = getResources().getConfiguration().locale;
+            final String raw = getString(R.string.menu_select).toUpperCase(locale);
+            mPick.setText(TextUtils.expandTemplate(raw, displayName));
+        } else {
+            mContainer.setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index f1dca1d..933dbe0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -104,7 +104,8 @@
         mRecentsRoot.authority = null;
         mRecentsRoot.rootId = null;
         mRecentsRoot.icon = R.drawable.ic_root_recent;
-        mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE;
+        mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE
+                | Root.FLAG_SUPPORTS_DIR_SELECTION;
         mRecentsRoot.title = mContext.getString(R.string.root_recent);
         mRecentsRoot.availableBytes = -1;
 
@@ -349,12 +350,15 @@
         final List<RootInfo> matching = Lists.newArrayList();
         for (RootInfo root : roots) {
             final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0;
+            final boolean supportsDir = (root.flags & Root.FLAG_SUPPORTS_DIR_SELECTION) != 0;
             final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0;
             final boolean localOnly = (root.flags & Root.FLAG_LOCAL_ONLY) != 0;
             final boolean empty = (root.flags & Root.FLAG_EMPTY) != 0;
 
             // Exclude read-only devices when creating
             if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
+            // Exclude roots that don't support directory picking
+            if (state.action == State.ACTION_PICK_DIRECTORY && !supportsDir) continue;
             // Exclude advanced devices when not requested
             if (!state.showAdvanced && advanced) continue;
             // Exclude non-local devices when local only
diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
deleted file mode 100644
index 1a47308..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * 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.documentsui;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.DocumentsContract;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class TestActivity extends Activity {
-    private static final String TAG = "TestActivity";
-
-    private static final int CODE_READ = 42;
-    private static final int CODE_WRITE = 43;
-
-    private TextView mResult;
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        final Context context = this;
-
-        final LinearLayout view = new LinearLayout(context);
-        view.setOrientation(LinearLayout.VERTICAL);
-
-        mResult = new TextView(context);
-        view.addView(mResult);
-
-        final CheckBox multiple = new CheckBox(context);
-        multiple.setText("ALLOW_MULTIPLE");
-        view.addView(multiple);
-        final CheckBox localOnly = new CheckBox(context);
-        localOnly.setText("LOCAL_ONLY");
-        view.addView(localOnly);
-
-        Button button;
-        button = new Button(context);
-        button.setText("OPEN_DOC */*");
-        button.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
-                intent.addCategory(Intent.CATEGORY_OPENABLE);
-                intent.setType("*/*");
-                if (multiple.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
-                }
-                if (localOnly.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
-                }
-                startActivityForResult(intent, CODE_READ);
-            }
-        });
-        view.addView(button);
-
-        button = new Button(context);
-        button.setText("OPEN_DOC image/*");
-        button.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
-                intent.addCategory(Intent.CATEGORY_OPENABLE);
-                intent.setType("image/*");
-                if (multiple.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
-                }
-                if (localOnly.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
-                }
-                startActivityForResult(intent, CODE_READ);
-            }
-        });
-        view.addView(button);
-
-        button = new Button(context);
-        button.setText("OPEN_DOC audio/ogg");
-        button.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
-                intent.addCategory(Intent.CATEGORY_OPENABLE);
-                intent.setType("audio/ogg");
-                if (multiple.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
-                }
-                if (localOnly.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
-                }
-                startActivityForResult(intent, CODE_READ);
-            }
-        });
-        view.addView(button);
-
-        button = new Button(context);
-        button.setText("OPEN_DOC text/plain, application/msword");
-        button.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
-                intent.addCategory(Intent.CATEGORY_OPENABLE);
-                intent.setType("*/*");
-                intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {
-                        "text/plain", "application/msword" });
-                if (multiple.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
-                }
-                if (localOnly.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
-                }
-                startActivityForResult(intent, CODE_READ);
-            }
-        });
-        view.addView(button);
-
-        button = new Button(context);
-        button.setText("CREATE_DOC text/plain");
-        button.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-                intent.addCategory(Intent.CATEGORY_OPENABLE);
-                intent.setType("text/plain");
-                intent.putExtra(Intent.EXTRA_TITLE, "foobar.txt");
-                if (localOnly.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
-                }
-                startActivityForResult(intent, CODE_WRITE);
-            }
-        });
-        view.addView(button);
-
-        button = new Button(context);
-        button.setText("CREATE_DOC image/png");
-        button.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-                intent.addCategory(Intent.CATEGORY_OPENABLE);
-                intent.setType("image/png");
-                intent.putExtra(Intent.EXTRA_TITLE, "mypicture.png");
-                if (localOnly.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
-                }
-                startActivityForResult(intent, CODE_WRITE);
-            }
-        });
-        view.addView(button);
-
-        button = new Button(context);
-        button.setText("GET_CONTENT */*");
-        button.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
-                intent.addCategory(Intent.CATEGORY_OPENABLE);
-                intent.setType("*/*");
-                if (multiple.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
-                }
-                if (localOnly.isChecked()) {
-                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
-                }
-                startActivityForResult(Intent.createChooser(intent, "Kittens!"), CODE_READ);
-            }
-        });
-        view.addView(button);
-
-        final ScrollView scroll = new ScrollView(context);
-        scroll.addView(view);
-
-        setContentView(scroll);
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        mResult.setText(null);
-        String result = "resultCode=" + resultCode + ", data=" + String.valueOf(data);
-
-        if (requestCode == CODE_READ) {
-            final Uri uri = data != null ? data.getData() : null;
-            if (uri != null) {
-                if (DocumentsContract.isDocumentUri(this, uri)) {
-                    result += "; DOC_ID";
-                }
-                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);
-                    final int length = Streams.readFullyNoClose(is).length;
-                    result += "; read length=" + length;
-                } catch (Exception e) {
-                    result += "; ERROR";
-                    Log.e(TAG, "Failed to read " + uri, e);
-                } finally {
-                    IoUtils.closeQuietly(is);
-                }
-            } else {
-                result += "no uri?";
-            }
-        } else if (requestCode == CODE_WRITE) {
-            final Uri uri = data != null ? data.getData() : null;
-            if (uri != null) {
-                if (DocumentsContract.isDocumentUri(this, uri)) {
-                    result += "; DOC_ID";
-                }
-                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.e(TAG, "Failed to write " + uri, e);
-                } finally {
-                    IoUtils.closeQuietly(os);
-                }
-            } else {
-                result += "no uri?";
-            }
-        }
-
-        Log.d(TAG, result);
-        mResult.setText(result);
-    }
-}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 559e052..16fc3e5 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -27,6 +27,7 @@
 import android.os.CancellationSignal;
 import android.os.Environment;
 import android.os.FileObserver;
+import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
@@ -143,7 +144,7 @@
                 final RootInfo root = new RootInfo();
                 root.rootId = rootId;
                 root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
-                        | Root.FLAG_SUPPORTS_SEARCH;
+                        | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_DIR_SELECTION;
                 if (ROOT_ID_PRIMARY_EMULATED.equals(rootId)) {
                     root.title = getContext().getString(R.string.root_internal_storage);
                 } else {
@@ -240,8 +241,8 @@
                 flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
             } else {
                 flags |= Document.FLAG_SUPPORTS_WRITE;
+                flags |= Document.FLAG_SUPPORTS_DELETE;
             }
-            flags |= Document.FLAG_SUPPORTS_DELETE;
         }
 
         final String displayName = file.getName();
@@ -284,11 +285,26 @@
     }
 
     @Override
+    public boolean isChildDocument(String parentDocId, String docId) {
+        try {
+            final File parent = getFileForDocId(parentDocId).getCanonicalFile();
+            final File doc = getFileForDocId(docId).getCanonicalFile();
+            return FileUtils.contains(parent, doc);
+        } catch (IOException e) {
+            throw new IllegalArgumentException(
+                    "Failed to determine if " + docId + " is child of " + parentDocId + ": " + e);
+        }
+    }
+
+    @Override
     public String createDocument(String docId, String mimeType, String displayName)
             throws FileNotFoundException {
         final File parent = getFileForDocId(docId);
-        File file;
+        if (!parent.isDirectory()) {
+            throw new IllegalArgumentException("Parent document isn't a directory");
+        }
 
+        File file;
         if (Document.MIME_TYPE_DIR.equals(mimeType)) {
             file = new File(parent, displayName);
             if (!file.mkdir()) {
@@ -317,6 +333,7 @@
 
     @Override
     public void deleteDocument(String docId) throws FileNotFoundException {
+        // TODO: extend to delete directories
         final File file = getFileForDocId(docId);
         if (!file.delete()) {
             throw new IllegalStateException("Failed to delete " + file);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java
index cb624ad..85befff 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUI.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java
@@ -35,6 +35,9 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
     }
 
+    protected void onBootCompleted() {
+    }
+
     @SuppressWarnings("unchecked")
     public <T> T getComponent(Class<T> interfaceType) {
         return (T) (mComponents != null ? mComponents.get(interfaceType) : null);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 0f55683..103991a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -17,7 +17,12 @@
 package com.android.systemui;
 
 import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Configuration;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import java.util.HashMap;
@@ -49,6 +54,7 @@
      */
     private final SystemUI[] mServices = new SystemUI[SERVICES.length];
     private boolean mServicesStarted;
+    private boolean mBootCompleted;
     private final Map<Class<?>, Object> mComponents = new HashMap<Class<?>, Object>();
 
     @Override
@@ -58,6 +64,23 @@
         // application theme in the manifest does only work for activities. Keep this in sync with
         // the theme set there.
         setTheme(R.style.systemui_theme);
+
+        registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (mBootCompleted) return;
+
+                if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
+                unregisterReceiver(this);
+                mBootCompleted = true;
+                if (mServicesStarted) {
+                    final int N = mServices.length;
+                    for (int i = 0; i < N; i++) {
+                        mServices[i].onBootCompleted();
+                    }
+                }
+            }
+        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
     }
 
     /**
@@ -71,6 +94,17 @@
         if (mServicesStarted) {
             return;
         }
+
+        if (!mBootCompleted) {
+            // check to see if maybe it was already completed long before we began
+            // see ActivityManagerService.finishBooting()
+            if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
+                mBootCompleted = true;
+                if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
+            }
+        }
+
+        Log.v(TAG, "Starting SystemUI services.");
         final int N = SERVICES.length;
         for (int i=0; i<N; i++) {
             Class<?> cl = SERVICES[i];
@@ -86,6 +120,10 @@
             mServices[i].mComponents = mComponents;
             if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
             mServices[i].start();
+
+            if (mBootCompleted) {
+                mServices[i].onBootCompleted();
+            }
         }
         mServicesStarted = true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 081e8de..ffdb620 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -157,24 +157,6 @@
     private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
 
     /**
-     * Allow the user to expand the status bar when the keyguard is engaged
-     * (without a pattern or password).
-     */
-    private static final boolean ENABLE_INSECURE_STATUS_BAR_EXPAND = true;
-
-    /**
-     * Allow the user to expand the status bar when a SECURE keyguard is engaged
-     * and {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS} is set
-     * (private notifications will be masked).
-     */
-    private static final boolean ENABLE_SECURE_STATUS_BAR_EXPAND = true;
-
-    /**
-     * Default value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}.
-     */
-    private static final boolean ALLOW_NOTIFICATIONS_DEFAULT = false;
-
-    /**
      * Secure setting whether analytics are collected on the keyguard.
      */
     private static final String KEYGUARD_ANALYTICS_SETTING = "keyguard_analytics";
@@ -277,11 +259,6 @@
     private int mLockSoundStreamId;
 
     /**
-     * Tracks value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}.
-     */
-    private boolean mAllowNotificationsWhenSecure;
-
-    /**
      * The volume applied to the lock/unlock sounds.
      */
     private float mLockSoundVolume;
@@ -895,13 +872,6 @@
             return;
         }
 
-        // note whether notification access should be allowed
-        mAllowNotificationsWhenSecure = ENABLE_SECURE_STATUS_BAR_EXPAND
-                && 0 != Settings.Global.getInt(
-                        mContext.getContentResolver(),
-                        Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS,
-                        ALLOW_NOTIFICATIONS_DEFAULT ? 1 : 0);
-
         // if the keyguard is already showing, don't bother
         if (mStatusBarKeyguardViewManager.isShowing()) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
@@ -1271,11 +1241,6 @@
                 // (like recents). Temporary enable/disable (e.g. the "back" button) are
                 // done in KeyguardHostView.
                 flags |= StatusBarManager.DISABLE_RECENT;
-                if (isSecure()) {
-                    // showing secure lockscreen; disable ticker and switch private notifications
-                    // to show their public versions, if available.
-                    flags |= StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS;
-                }
                 if (!isAssistantAvailable()) {
                     flags |= StatusBarManager.DISABLE_SEARCH;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 10b6d49..21c2926 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -26,6 +26,7 @@
 import android.graphics.Paint;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.DisplayMetrics;
@@ -45,6 +46,7 @@
     // Which recents to use
     boolean mUseAlternateRecents;
     AlternateRecentsComponent mAlternateRecents;
+    boolean mBootCompleted = false;
 
     @Override
     public void start() {
@@ -60,6 +62,11 @@
     }
 
     @Override
+    protected void onBootCompleted() {
+        mBootCompleted = true;
+    }
+
+    @Override
     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
         if (mUseAlternateRecents) {
             // Launch the alternate recents if required
@@ -197,13 +204,11 @@
                                 Intent intent =
                                         new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT);
                                 intent.setPackage("com.android.systemui");
-                                mContext.sendBroadcastAsUser(intent,
-                                        new UserHandle(UserHandle.USER_CURRENT));
+                                sendBroadcastSafely(intent);
                             }
                         });
                 intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true);
-                mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
-                        UserHandle.USER_CURRENT));
+                startActivitySafely(intent, opts.toBundle());
             }
         } catch (ActivityNotFoundException e) {
             Log.e(TAG, "Failed to launch RecentAppsIntent", e);
@@ -225,7 +230,7 @@
             Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
             intent.setClassName("com.android.systemui",
                     "com.android.systemui.recent.RecentsPreloadReceiver");
-            mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+            sendBroadcastSafely(intent);
 
             RecentTasksLoader.getInstance(mContext).preloadFirstTask();
         }
@@ -239,7 +244,7 @@
             Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
             intent.setClassName("com.android.systemui",
                     "com.android.systemui.recent.RecentsPreloadReceiver");
-            mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+            sendBroadcastSafely(intent);
 
             RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
         }
@@ -252,9 +257,25 @@
         } else {
             Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
             intent.setPackage("com.android.systemui");
-            mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+            sendBroadcastSafely(intent);
 
             RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
         }
     }
+
+    /**
+     * Send broadcast only if BOOT_COMPLETED
+     */
+    private void sendBroadcastSafely(Intent intent) {
+        if (!mBootCompleted) return;
+        mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+    }
+
+    /**
+     * Start activity only if BOOT_COMPLETED
+     */
+    private void startActivitySafely(Intent intent, Bundle opts) {
+        if (!mBootCompleted) return;
+        mContext.startActivityAsUser(intent, opts, new UserHandle(UserHandle.USER_CURRENT));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 56f83df..bb481ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -172,7 +172,7 @@
         int oldHeight = lp.height;
         lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
         setLayoutParams(lp);
-        measure(View.MeasureSpec.makeMeasureSpec(getMeasuredWidth(), View.MeasureSpec.EXACTLY),
+        measure(View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY),
                 View.MeasureSpec.makeMeasureSpec(mRowMaxHeight, View.MeasureSpec.AT_MOST));
         lp.height = oldHeight;
         setLayoutParams(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index cf31b44..1ffb4ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -143,6 +143,10 @@
         return false;
     }
 
+    public boolean isSecure() {
+        return mKeyguardView == null || mKeyguardView.getSecurityMode() != SecurityMode.None;
+    }
+
     public boolean onMenuPressed() {
         ensureView();
         if (mKeyguardView.handleMenuKey()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 2d96c5e..7f1ddaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -25,7 +25,6 @@
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
-import static com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -77,7 +76,6 @@
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewPropertyAnimator;
-import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.animation.AccelerateInterpolator;
@@ -148,6 +146,11 @@
     private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService
     private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
 
+    /**
+     * Default value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}.
+     */
+    private static final boolean ALLOW_NOTIFICATIONS_DEFAULT = false;
+
     private static final int STATUS_OR_NAV_TRANSIENT =
             View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
     private static final long AUTOHIDE_TIMEOUT_MS = 3000;
@@ -781,10 +784,11 @@
             if (!qsTap && !inButton) {
                 mSettingsTracker.computeCurrentVelocity(1000);
                 final float vy = mSettingsTracker.getYVelocity();
+                final boolean animate = true;
                 if (dy <= slop || vy <= 0) {
-                    flipToNotifications();
+                    flipToNotifications(animate);
                 } else {
-                    flipToSettings();
+                    flipToSettings(animate);
                 }
             }
             mSettingsTracker.recycle();
@@ -1452,8 +1456,6 @@
         flagdbg.append(((diff  & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " ");
         flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts");
         flagdbg.append(((diff  & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " ");
-        flagdbg.append(((state & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) ? "PRIVATE" : "private");
-        flagdbg.append(((diff  & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) ? "* " : " ");
         flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info");
         flagdbg.append(((diff  & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " ");
         flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back");
@@ -1538,15 +1540,6 @@
                     .setDuration(175)
                     .start();
             }
-        } else if ((diff & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) {
-            if ((state & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) {
-                // we are outside a secure keyguard, so we need to switch to "public" mode
-                setLockscreenPublicMode(true);
-            } else {
-                // user has authenticated the device; full notifications may be shown
-                setLockscreenPublicMode(false);
-            }
-            updateNotificationIcons();
         }
     }
 
@@ -1669,7 +1662,7 @@
             mStatusBarWindow.cancelExpandHelper();
             mStatusBarView.collapseAllPanels(true);
             if (isFlippedToSettings()) {
-                flipToNotifications();
+                flipToNotifications(true /*animate*/);
             }
         }
     }
@@ -1729,36 +1722,49 @@
 
         mNotificationPanel.expand();
         if (mStackScroller.getVisibility() != View.VISIBLE) {
-            flipToNotifications();
+            flipToNotifications(true /*animate*/);
         }
 
         if (false) postStartTracing();
     }
 
-    public void flipToNotifications() {
-        if (mFlipSettingsViewAnim != null) mFlipSettingsViewAnim.cancel();
-        if (mScrollViewAnim != null) mScrollViewAnim.cancel();
+    private static void cancelAnim(Animator anim) {
+        if (anim != null) {
+            anim.cancel();
+        }
+    }
+
+    public void flipToNotifications(boolean animate) {
+        cancelAnim(mFlipSettingsViewAnim);
+        cancelAnim(mScrollViewAnim);
+        cancelAnim(mClearButtonAnim);
         mHeaderFlipper.cancel();
         mKeyguardFlipper.cancel();
-        if (mClearButtonAnim != null) mClearButtonAnim.cancel();
         mStackScroller.setVisibility(View.VISIBLE);
         final int h = mNotificationPanel.getMeasuredHeight();
-        final float settingsY = mSettingsTracker != null ? mFlipSettingsView.getTranslationY() : 0;
-        final float scrollerY = mSettingsTracker != null ? mStackScroller.getTranslationY() : h;
-        mScrollViewAnim = start(
-                interpolator(mDecelerateInterpolator,
-                    ObjectAnimator.ofFloat(mStackScroller, View.TRANSLATION_Y, scrollerY, 0)
-                        .setDuration(FLIP_DURATION)
-                    ));
-        mFlipSettingsViewAnim = start(
-            setVisibilityWhenDone(
-                interpolator(mDecelerateInterpolator,
-                        ObjectAnimator.ofFloat(mFlipSettingsView, View.TRANSLATION_Y, settingsY, -h)
-                        )
-                    .setDuration(FLIP_DURATION),
-                mFlipSettingsView, View.INVISIBLE));
-        mHeaderFlipper.flipToNotifications();
-        mKeyguardFlipper.flipToNotifications();
+        if (animate) {
+            final float settingsY =
+                    mSettingsTracker != null ? mFlipSettingsView.getTranslationY() : 0;
+            final float scrollerY = mSettingsTracker != null ? mStackScroller.getTranslationY() : h;
+            mScrollViewAnim = start(
+                    interpolator(mDecelerateInterpolator,
+                        ObjectAnimator.ofFloat(mStackScroller, View.TRANSLATION_Y, scrollerY, 0)
+                            .setDuration(FLIP_DURATION)
+                        ));
+            mFlipSettingsViewAnim = start(
+                setVisibilityWhenDone(
+                    interpolator(mDecelerateInterpolator,
+                            ObjectAnimator.ofFloat(
+                                    mFlipSettingsView, View.TRANSLATION_Y, settingsY, -h))
+                        .setDuration(FLIP_DURATION),
+                    mFlipSettingsView, View.INVISIBLE));
+        } else {
+            mStackScroller.setTranslationY(0);
+            mFlipSettingsView.setTranslationY(-h);
+            mFlipSettingsView.setVisibility(View.INVISIBLE);
+        }
+        mHeaderFlipper.flipToNotifications(animate);
+        mKeyguardFlipper.flipToNotifications(animate);
         mClearButton.setVisibility(View.VISIBLE);
         mClearButton.setAlpha(0f);
         setAreThereNotifications(); // this will show/hide the button as necessary
@@ -1766,7 +1772,7 @@
             public void run() {
                 updateCarrierLabelVisibility(false);
             }
-        }, FLIP_DURATION - 150);
+        }, animate ? FLIP_DURATION - 150 : 0);
         if (mOnFlipRunnable != null) {
             mOnFlipRunnable.run();
         }
@@ -1785,7 +1791,7 @@
         mNotificationPanel.expand();
         if (mFlipSettingsView.getVisibility() != View.VISIBLE
                 || mFlipSettingsView.getTranslationY() < 0) {
-            flipToSettings();
+            flipToSettings(true /*animate*/);
         }
 
         if (false) postStartTracing();
@@ -1798,58 +1804,63 @@
         return false;
     }
 
-    public void flipToSettings() {
+    public void flipToSettings(boolean animate) {
         // Settings are not available in setup
         if (!mUserSetup) return;
 
-        if (mFlipSettingsViewAnim != null) mFlipSettingsViewAnim.cancel();
-        if (mScrollViewAnim != null) mScrollViewAnim.cancel();
+        cancelAnim(mFlipSettingsViewAnim);
+        cancelAnim(mScrollViewAnim);
         mHeaderFlipper.cancel();
         mKeyguardFlipper.cancel();
-        if (mClearButtonAnim != null) mClearButtonAnim.cancel();
+        cancelAnim(mClearButtonAnim);
 
         mFlipSettingsView.setVisibility(View.VISIBLE);
         final int h = mNotificationPanel.getMeasuredHeight();
-        final float settingsY = mSettingsTracker != null ? mFlipSettingsView.getTranslationY() : -h;
-        final float scrollerY = mSettingsTracker != null ? mStackScroller.getTranslationY() : 0;
-        mFlipSettingsViewAnim = start(
-                startDelay(0,
+        if (animate) {
+            final float settingsY
+                    = mSettingsTracker != null ? mFlipSettingsView.getTranslationY() : -h;
+            final float scrollerY = mSettingsTracker != null ? mStackScroller.getTranslationY() : 0;
+            mFlipSettingsViewAnim = start(
+                    startDelay(0,
+                        interpolator(mDecelerateInterpolator,
+                            ObjectAnimator.ofFloat(mFlipSettingsView, View.TRANSLATION_Y,
+                                    settingsY, 0f)
+                                .setDuration(FLIP_DURATION)
+                            )));
+            mScrollViewAnim = start(
+                setVisibilityWhenDone(
                     interpolator(mDecelerateInterpolator,
-                        ObjectAnimator.ofFloat(mFlipSettingsView, View.TRANSLATION_Y, settingsY, 0f)
-                            .setDuration(FLIP_DURATION)
-                        )));
-        mScrollViewAnim = start(
-            setVisibilityWhenDone(
-                interpolator(mDecelerateInterpolator,
-                        ObjectAnimator.ofFloat(mStackScroller, View.TRANSLATION_Y, scrollerY, h)
-                        )
+                            ObjectAnimator.ofFloat(mStackScroller, View.TRANSLATION_Y, scrollerY, h)
+                            )
+                        .setDuration(FLIP_DURATION),
+                        mStackScroller, View.INVISIBLE));
+        } else {
+            mFlipSettingsView.setTranslationY(0);
+            mStackScroller.setTranslationY(h);
+            mStackScroller.setVisibility(View.INVISIBLE);
+        }
+        mHeaderFlipper.flipToSettings(animate);
+        mKeyguardFlipper.flipToSettings(animate);
+        if (animate) {
+            mClearButtonAnim = start(
+                setVisibilityWhenDone(
+                    ObjectAnimator.ofFloat(mClearButton, View.ALPHA, 0f)
                     .setDuration(FLIP_DURATION),
-                    mStackScroller, View.INVISIBLE));
-        mHeaderFlipper.flipToSettings();
-        mKeyguardFlipper.flipToSettings();
-        mClearButtonAnim = start(
-            setVisibilityWhenDone(
-                ObjectAnimator.ofFloat(mClearButton, View.ALPHA, 0f)
-                .setDuration(FLIP_DURATION),
-                mClearButton, View.INVISIBLE));
+                    mClearButton, View.INVISIBLE));
+        } else {
+            mClearButton.setAlpha(0);
+            mClearButton.setVisibility(View.INVISIBLE);
+        }
         mNotificationPanel.postDelayed(new Runnable() {
             public void run() {
                 updateCarrierLabelVisibility(false);
             }
-        }, FLIP_DURATION - 150);
+        }, animate ? FLIP_DURATION - 150 : 0);
         if (mOnFlipRunnable != null) {
             mOnFlipRunnable.run();
         }
     }
 
-    public void flipPanels() {
-        if (mFlipSettingsView.getVisibility() != View.VISIBLE) {
-            flipToSettings();
-        } else {
-            flipToNotifications();
-        }
-    }
-
     public void animateCollapseQuickSettings() {
         mStatusBarView.collapseAllPanels(true);
     }
@@ -2910,31 +2921,44 @@
 
     public void showKeyguard() {
         mOnKeyguard = true;
+        updateKeyguardState();
         instantExpandNotificationsPanel();
-        if (isFlippedToSettings()) {
-            flipToNotifications();
-        }
-        mKeyguardStatusView.setVisibility(View.VISIBLE);
-        mKeyguardBottomArea.setVisibility(View.VISIBLE);
-        mNotificationPanelHeader.setVisibility(View.GONE);
-
-        mKeyguardFlipper.setVisibility(View.VISIBLE);
-        mSettingsContainer.setKeyguardShowing(true);
-        updateRowStates();
-        checkBarModes();
     }
 
     public void hideKeyguard() {
         mOnKeyguard = false;
-        mKeyguardStatusView.setVisibility(View.GONE);
-        mKeyguardBottomArea.setVisibility(View.GONE);
-        mNotificationPanelHeader.setVisibility(View.VISIBLE);
-
-        mKeyguardFlipper.setVisibility(View.GONE);
-        mSettingsContainer.setKeyguardShowing(false);
-        updateRowStates();
+        updateKeyguardState();
         instantCollapseNotificationPanel();
+    }
+
+    private void updatePublicMode() {
+        setLockscreenPublicMode(mOnKeyguard && mStatusBarKeyguardViewManager.isSecure());
+    }
+
+    private void updateKeyguardState() {
+        if (mOnKeyguard) {
+            if (isFlippedToSettings()) {
+                flipToNotifications(false /*animate*/);
+            }
+            mKeyguardStatusView.setVisibility(View.VISIBLE);
+            mKeyguardBottomArea.setVisibility(View.VISIBLE);
+            mNotificationPanelHeader.setVisibility(View.GONE);
+
+            mKeyguardFlipper.setVisibility(View.VISIBLE);
+            mSettingsContainer.setKeyguardShowing(true);
+        } else {
+            mKeyguardStatusView.setVisibility(View.GONE);
+            mKeyguardBottomArea.setVisibility(View.GONE);
+            mNotificationPanelHeader.setVisibility(View.VISIBLE);
+
+            mKeyguardFlipper.setVisibility(View.GONE);
+            mSettingsContainer.setKeyguardShowing(false);
+        }
+
+        updatePublicMode();
+        updateRowStates();
         checkBarModes();
+        updateNotificationIcons();
     }
 
     public void userActivity() {
@@ -3075,36 +3099,48 @@
             }
         }
 
-        public void flipToSettings() {
-            mSettingsButtonAnim = start(
-                setVisibilityWhenDone(
-                    ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 0f)
-                        .setDuration(FLIP_DURATION_OUT),
-                    mStackScroller, View.INVISIBLE));
+        public void flipToSettings(boolean animate) {
             mNotificationButton.setVisibility(View.VISIBLE);
-            mNotificationButtonAnim = start(
-                startDelay(FLIP_DURATION_OUT,
-                    ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 1f)
-                        .setDuration(FLIP_DURATION_IN)));
+            if (animate) {
+                mSettingsButtonAnim = start(
+                    setVisibilityWhenDone(
+                        ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 0f)
+                            .setDuration(FLIP_DURATION_OUT),
+                        mStackScroller, View.INVISIBLE));
+                mNotificationButtonAnim = start(
+                    startDelay(FLIP_DURATION_OUT,
+                        ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 1f)
+                            .setDuration(FLIP_DURATION_IN)));
+            } else {
+                mSettingsButton.setAlpha(0f);
+                mSettingsButton.setVisibility(View.INVISIBLE);
+                mNotificationButton.setAlpha(1f);
+            }
         }
 
-        public void flipToNotifications() {
-            mNotificationButtonAnim = start(
-                setVisibilityWhenDone(
-                    ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 0f)
-                        .setDuration(FLIP_DURATION_OUT),
-                    mNotificationButton, View.INVISIBLE));
-
+        public void flipToNotifications(boolean animate) {
             mSettingsButton.setVisibility(View.VISIBLE);
-            mSettingsButtonAnim = start(
-                startDelay(FLIP_DURATION_OUT,
-                    ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 1f)
-                        .setDuration(FLIP_DURATION_IN)));
+            if (animate) {
+                mNotificationButtonAnim = start(
+                    setVisibilityWhenDone(
+                        ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 0f)
+                            .setDuration(FLIP_DURATION_OUT),
+                        mNotificationButton, View.INVISIBLE));
+
+                mSettingsButtonAnim = start(
+                    startDelay(FLIP_DURATION_OUT,
+                        ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 1f)
+                            .setDuration(FLIP_DURATION_IN)));
+            } else {
+                mNotificationButton.setVisibility(View.INVISIBLE);
+                mNotificationButton.setAlpha(0f);
+                mSettingsButton.setAlpha(1f);
+            }
         }
 
         public void cancel() {
-            if (mSettingsButtonAnim != null) mSettingsButtonAnim.cancel();
-            if (mNotificationButtonAnim != null) mNotificationButtonAnim.cancel();
+            cancelAnim(mSettingsButtonAnim);
+            cancelAnim(mNotificationButtonAnim);
         }
 
         public void setVisibility(int vis) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index b3fba76..f3ebf1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -280,6 +280,7 @@
         addUserTiles(mContainerView, inflater);
         addSystemTiles(mContainerView, inflater);
         addTemporaryTiles(mContainerView, inflater);
+        addAccessibilityTiles(mContainerView);
 
         queryForUserInformation();
         queryForSslCaCerts();
@@ -311,6 +312,34 @@
         collapsePanels();
     }
 
+    private void addAccessibilityTiles(ViewGroup parent) {
+        if (!DEBUG_GONE_TILES && !SHOW_ACCESSIBILITY_TILES) return;
+
+        // Color inversion tile
+        final SystemSettingTile inversionTile = new SystemSettingTile(mContext);
+        inversionTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+                SystemSettingTile.TYPE_SECURE);
+        inversionTile.setFragment("Settings$AccessibilityInversionSettingsActivity");
+        mModel.addInversionTile(inversionTile, inversionTile.getRefreshCallback());
+        parent.addView(inversionTile);
+
+        // Contrast enhancement tile
+        final SystemSettingTile contrastTile = new SystemSettingTile(mContext);
+        contrastTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED,
+                SystemSettingTile.TYPE_SECURE);
+        contrastTile.setFragment("Settings$AccessibilityContrastSettingsActivity");
+        mModel.addContrastTile(contrastTile, contrastTile.getRefreshCallback());
+        parent.addView(contrastTile);
+
+        // Color space adjustment tile
+        final SystemSettingTile colorSpaceTile = new SystemSettingTile(mContext);
+        colorSpaceTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+                SystemSettingTile.TYPE_SECURE);
+        colorSpaceTile.setFragment("Settings$AccessibilityDaltonizerSettingsActivity");
+        mModel.addColorSpaceTile(colorSpaceTile, colorSpaceTile.getRefreshCallback());
+        parent.addView(colorSpaceTile);
+    }
+
     private void addUserTiles(final ViewGroup parent, final LayoutInflater inflater) {
         QuickSettingsTileView userTile = (QuickSettingsTileView)
                 inflater.inflate(R.layout.quick_settings_tile, parent, false);
@@ -384,35 +413,6 @@
                 new QuickSettingsModel.BasicRefreshCallback(settingsTile));
         parent.addView(settingsTile);
         mDynamicSpannedTiles.add(settingsTile);
-
-        if (SHOW_ACCESSIBILITY_TILES) {
-            // Color inversion tile
-            final SystemSettingTile inversionTile = new SystemSettingTile(mContext);
-            inversionTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
-                    SystemSettingTile.TYPE_SECURE);
-            inversionTile.setFragment("Settings$AccessibilityInversionSettingsActivity");
-            mModel.addInversionTile(inversionTile, inversionTile.getRefreshCallback());
-            parent.addView(inversionTile);
-            mDynamicSpannedTiles.add(inversionTile);
-
-            // Contrast enhancement tile
-            final SystemSettingTile contrastTile = new SystemSettingTile(mContext);
-            contrastTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED,
-                    SystemSettingTile.TYPE_SECURE);
-            contrastTile.setFragment("Settings$AccessibilityContrastSettingsActivity");
-            mModel.addContrastTile(contrastTile, contrastTile.getRefreshCallback());
-            parent.addView(contrastTile);
-            mDynamicSpannedTiles.add(contrastTile);
-
-            // Color space adjustment tile
-            final SystemSettingTile colorSpaceTile = new SystemSettingTile(mContext);
-            colorSpaceTile.setUri(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
-                    SystemSettingTile.TYPE_SECURE);
-            colorSpaceTile.setFragment("Settings$AccessibilityDaltonizerSettingsActivity");
-            mModel.addColorSpaceTile(colorSpaceTile, colorSpaceTile.getRefreshCallback());
-            parent.addView(colorSpaceTile);
-            mDynamicSpannedTiles.add(colorSpaceTile);
-        }
     }
 
     private void addSystemTiles(ViewGroup parent, LayoutInflater inflater) {
@@ -574,49 +574,6 @@
         });
         parent.addView(batteryTile);
 
-        // Airplane Mode
-        final QuickSettingsBasicTile airplaneTile
-                = new QuickSettingsBasicTile(mContext);
-        mModel.addAirplaneModeTile(airplaneTile, new QuickSettingsModel.RefreshCallback() {
-            @Override
-            public void refreshView(QuickSettingsTileView unused, State state) {
-                airplaneTile.setImageResource(state.iconId);
-
-                String airplaneState = mContext.getString(
-                        (state.enabled) ? R.string.accessibility_desc_on
-                                : R.string.accessibility_desc_off);
-                airplaneTile.setContentDescription(
-                        mContext.getString(R.string.accessibility_quick_settings_airplane, airplaneState));
-                airplaneTile.setText(state.label);
-            }
-        });
-        parent.addView(airplaneTile);
-
-        // Zen Mode
-        final QuickSettingsBasicTile zenModeTile = new QuickSettingsBasicTile(mContext);
-        zenModeTile.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                showZenModeDialog();
-            }
-        });
-        mModel.addZenModeTile(zenModeTile, new QuickSettingsModel.RefreshCallback() {
-            @Override
-            public void refreshView(QuickSettingsTileView unused, State state) {
-                zenModeTile.setImageResource(state.iconId);
-                // TODO cut new assets
-                zenModeTile.getImageView().setAlpha(state.enabled ? 1 : .2f);
-                zenModeTile.getImageView().setScaleX(1.5f);
-                zenModeTile.getImageView().setScaleY(1.5f);
-                // for landscape version
-                zenModeTile.getTextView().setMaxLines(2);
-                zenModeTile.getTextView().setEllipsize(TruncateAt.END);
-                // TODO content description
-                zenModeTile.setText(state.label);
-            }
-        });
-        parent.addView(zenModeTile);
-
         // Bluetooth
         if (mModel.deviceSupportsBluetooth()
                 || DEBUG_GONE_TILES) {
@@ -710,6 +667,50 @@
             }
         });
         parent.addView(locationTile);
+
+        // Airplane Mode
+        final QuickSettingsBasicTile airplaneTile
+                = new QuickSettingsBasicTile(mContext);
+        mModel.addAirplaneModeTile(airplaneTile, new QuickSettingsModel.RefreshCallback() {
+            @Override
+            public void refreshView(QuickSettingsTileView unused, State state) {
+                airplaneTile.setImageResource(state.iconId);
+
+                String airplaneState = mContext.getString(
+                        (state.enabled) ? R.string.accessibility_desc_on
+                                : R.string.accessibility_desc_off);
+                airplaneTile.setContentDescription(
+                        mContext.getString(R.string.accessibility_quick_settings_airplane,
+                                airplaneState));
+                airplaneTile.setText(state.label);
+            }
+        });
+        parent.addView(airplaneTile);
+
+        // Zen Mode
+        final QuickSettingsBasicTile zenModeTile = new QuickSettingsBasicTile(mContext);
+        zenModeTile.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showZenModeDialog();
+            }
+        });
+        mModel.addZenModeTile(zenModeTile, new QuickSettingsModel.RefreshCallback() {
+            @Override
+            public void refreshView(QuickSettingsTileView unused, State state) {
+                zenModeTile.setImageResource(state.iconId);
+                // TODO cut new assets
+                zenModeTile.getImageView().setAlpha(state.enabled ? 1 : .2f);
+                zenModeTile.getImageView().setScaleX(1.5f);
+                zenModeTile.getImageView().setScaleY(1.5f);
+                // for landscape version
+                zenModeTile.getTextView().setMaxLines(2);
+                zenModeTile.getTextView().setEllipsize(TruncateAt.END);
+                // TODO content description
+                zenModeTile.setText(state.label);
+            }
+        });
+        parent.addView(zenModeTile);
     }
 
     private void addTemporaryTiles(final ViewGroup parent, final LayoutInflater inflater) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 501d3f2..c2595cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -179,6 +179,10 @@
         }
     }
 
+    public boolean isSecure() {
+        return mBouncer.isSecure();
+    }
+
     /**
      * @return Whether the keyguard is showing
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index a7363f4..d9e7f66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -533,7 +533,23 @@
             } else {
 
                 // We are expanding the shade, expand it to its full height.
-                mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
+                if (mFirstChildWhileExpanding.getWidth() == 0) {
+
+                    // This child was not layouted yet, wait for a layout pass
+                    mFirstChildWhileExpanding
+                            .addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+                                @Override
+                                public void onLayoutChange(View v, int left, int top, int right,
+                                        int bottom, int oldLeft, int oldTop, int oldRight,
+                                        int oldBottom) {
+                                    mFirstChildMaxHeight = getMaxAllowedChildHeight(
+                                            mFirstChildWhileExpanding);
+                                    mFirstChildWhileExpanding.removeOnLayoutChangeListener(this);
+                                }
+                            });
+                } else {
+                    mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
+                }
             }
         } else {
             mFirstChildMaxHeight = 0;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8d1a24a..8fa076b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17,8 +17,10 @@
 package com.android.server.am;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
@@ -724,6 +726,7 @@
     private static final String ATTR_URI = "uri";
     private static final String ATTR_MODE_FLAGS = "modeFlags";
     private static final String ATTR_CREATED_TIME = "createdTime";
+    private static final String ATTR_PREFIX = "prefix";
 
     /**
      * Global set of specific {@link Uri} permissions that have been granted.
@@ -731,8 +734,41 @@
      * to {@link UriPermission#uri} to {@link UriPermission}.
      */
     @GuardedBy("this")
-    private final SparseArray<ArrayMap<Uri, UriPermission>>
-            mGrantedUriPermissions = new SparseArray<ArrayMap<Uri, UriPermission>>();
+    private final SparseArray<ArrayMap<GrantUri, UriPermission>>
+            mGrantedUriPermissions = new SparseArray<ArrayMap<GrantUri, UriPermission>>();
+
+    public static class GrantUri {
+        public final Uri uri;
+        public final boolean prefix;
+
+        public GrantUri(Uri uri, boolean prefix) {
+            this.uri = uri;
+            this.prefix = prefix;
+        }
+
+        @Override
+        public int hashCode() {
+            return toString().hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof GrantUri) {
+                GrantUri other = (GrantUri) o;
+                return uri.equals(other.uri) && prefix == other.prefix;
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            if (prefix) {
+                return uri.toString() + " [prefix]";
+            } else {
+                return uri.toString();
+            }
+        }
+    }
 
     CoreSettingsObserver mCoreSettingsObserver;
 
@@ -5839,7 +5875,7 @@
      * in {@link ContentProvider}.
      */
     private final boolean checkHoldingPermissionsLocked(
-            IPackageManager pm, ProviderInfo pi, Uri uri, int uid, int modeFlags) {
+            IPackageManager pm, ProviderInfo pi, Uri uri, int uid, final int modeFlags) {
         if (DEBUG_URI_PERMISSION) Slog.v(TAG,
                 "checkHoldingPermissionsLocked: uri=" + uri + " uid=" + uid);
 
@@ -5935,18 +5971,17 @@
         return pi;
     }
 
-    private UriPermission findUriPermissionLocked(int targetUid, Uri uri) {
-        ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
+    private UriPermission findUriPermissionLocked(int targetUid, GrantUri uri) {
+        final ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
         if (targetUris != null) {
             return targetUris.get(uri);
-        } else {
-            return null;
         }
+        return null;
     }
 
-    private UriPermission findOrCreateUriPermissionLocked(
-            String sourcePkg, String targetPkg, int targetUid, Uri uri) {
-        ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
+    private UriPermission findOrCreateUriPermissionLocked(String sourcePkg,
+            String targetPkg, int targetUid, GrantUri uri) {
+        ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
         if (targetUris == null) {
             targetUris = Maps.newArrayMap();
             mGrantedUriPermissions.put(targetUid, targetUris);
@@ -5961,21 +5996,40 @@
         return perm;
     }
 
-    private final boolean checkUriPermissionLocked(
-            Uri uri, int uid, int modeFlags, int minStrength) {
+    private final boolean checkUriPermissionLocked(Uri uri, int uid, final int modeFlags) {
+        final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+        final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE
+                : UriPermission.STRENGTH_OWNED;
+
         // Root gets to do everything.
         if (uid == 0) {
             return true;
         }
-        ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid);
+
+        final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid);
         if (perms == null) return false;
-        UriPermission perm = perms.get(uri);
-        if (perm == null) return false;
-        return perm.getStrength(modeFlags) >= minStrength;
+
+        // First look for exact match
+        final UriPermission exactPerm = perms.get(new GrantUri(uri, false));
+        if (exactPerm != null && exactPerm.getStrength(modeFlags) >= minStrength) {
+            return true;
+        }
+
+        // No exact match, look for prefixes
+        final int N = perms.size();
+        for (int i = 0; i < N; i++) {
+            final UriPermission perm = perms.valueAt(i);
+            if (perm.uri.prefix && uri.isPathPrefixMatch(perm.uri.uri)
+                    && perm.getStrength(modeFlags) >= minStrength) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     @Override
-    public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+    public int checkUriPermission(Uri uri, int pid, int uid, final int modeFlags) {
         enforceNotIsolatedCaller("checkUriPermission");
 
         // Another redirected-binder-call permissions check as in
@@ -5990,8 +6044,8 @@
         if (pid == MY_PID) {
             return PackageManager.PERMISSION_GRANTED;
         }
-        synchronized(this) {
-            return checkUriPermissionLocked(uri, uid, modeFlags, UriPermission.STRENGTH_OWNED)
+        synchronized (this) {
+            return checkUriPermissionLocked(uri, uid, modeFlags)
                     ? PackageManager.PERMISSION_GRANTED
                     : PackageManager.PERMISSION_DENIED;
         }
@@ -6007,11 +6061,8 @@
      * lastTargetUid else set that to -1.
      */
     int checkGrantUriPermissionLocked(int callingUid, String targetPkg,
-            Uri uri, int modeFlags, int lastTargetUid) {
-        final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
-        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        if (modeFlags == 0) {
+            Uri uri, final int modeFlags, int lastTargetUid) {
+        if (!Intent.isAccessUriMode(modeFlags)) {
             return -1;
         }
 
@@ -6106,9 +6157,7 @@
         if (callingUid != Process.myUid()) {
             if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
                 // Require they hold a strong enough Uri permission
-                final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE
-                        : UriPermission.STRENGTH_OWNED;
-                if (!checkUriPermissionLocked(uri, callingUid, modeFlags, minStrength)) {
+                if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
                     throw new SecurityException("Uid " + callingUid
                             + " does not have permission to uri " + uri);
                 }
@@ -6120,19 +6169,16 @@
 
     @Override
     public int checkGrantUriPermission(int callingUid, String targetPkg,
-            Uri uri, int modeFlags) {
+            Uri uri, final int modeFlags) {
         enforceNotIsolatedCaller("checkGrantUriPermission");
         synchronized(this) {
             return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags, -1);
         }
     }
 
-    void grantUriPermissionUncheckedLocked(
-            int targetUid, String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
-        final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
-        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        if (modeFlags == 0) {
+    void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg, Uri uri,
+            final int modeFlags, UriPermissionOwner owner) {
+        if (!Intent.isAccessUriMode(modeFlags)) {
             return;
         }
 
@@ -6150,13 +6196,14 @@
             return;
         }
 
+        final boolean prefix = (modeFlags & Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) != 0;
         final UriPermission perm = findOrCreateUriPermissionLocked(
-                pi.packageName, targetPkg, targetUid, uri);
-        perm.grantModes(modeFlags, persistable, owner);
+                pi.packageName, targetPkg, targetUid, new GrantUri(uri, prefix));
+        perm.grantModes(modeFlags, owner);
     }
 
     void grantUriPermissionLocked(int callingUid, String targetPkg, Uri uri,
-            int modeFlags, UriPermissionOwner owner) {
+            final int modeFlags, UriPermissionOwner owner) {
         if (targetPkg == null) {
             throw new NullPointerException("targetPkg");
         }
@@ -6270,7 +6317,7 @@
 
     @Override
     public void grantUriPermission(IApplicationThread caller, String targetPkg,
-            Uri uri, int modeFlags) {
+            Uri uri, final int modeFlags) {
         enforceNotIsolatedCaller("grantUriPermission");
         synchronized(this) {
             final ProcessRecord r = getRecordForAppLocked(caller);
@@ -6286,32 +6333,32 @@
                 throw new IllegalArgumentException("null uri");
             }
 
-            // Persistable only supported through Intents
-            Preconditions.checkFlagsArgument(modeFlags,
-                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+            Preconditions.checkFlagsArgument(modeFlags, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
 
-            grantUriPermissionLocked(r.uid, targetPkg, uri, modeFlags,
-                    null);
+            grantUriPermissionLocked(r.uid, targetPkg, uri, modeFlags, null);
         }
     }
 
     void removeUriPermissionIfNeededLocked(UriPermission perm) {
-        if ((perm.modeFlags&(Intent.FLAG_GRANT_READ_URI_PERMISSION
-                |Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) == 0) {
-            ArrayMap<Uri, UriPermission> perms
-                    = mGrantedUriPermissions.get(perm.targetUid);
+        if (perm.modeFlags == 0) {
+            final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(
+                    perm.targetUid);
             if (perms != null) {
                 if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
                         "Removing " + perm.targetUid + " permission to " + perm.uri);
+
                 perms.remove(perm.uri);
-                if (perms.size() == 0) {
+                if (perms.isEmpty()) {
                     mGrantedUriPermissions.remove(perm.targetUid);
                 }
             }
         }
     }
 
-    private void revokeUriPermissionLocked(int callingUid, Uri uri, int modeFlags) {
+    private void revokeUriPermissionLocked(int callingUid, Uri uri, final int modeFlags) {
         if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + uri);
 
         final IPackageManager pm = AppGlobals.getPackageManager();
@@ -6335,46 +6382,29 @@
         boolean persistChanged = false;
 
         // Go through all of the permissions and remove any that match.
-        final List<String> SEGMENTS = uri.getPathSegments();
-        if (SEGMENTS != null) {
-            final int NS = SEGMENTS.size();
-            int N = mGrantedUriPermissions.size();
-            for (int i=0; i<N; i++) {
-                ArrayMap<Uri, UriPermission> perms
-                        = mGrantedUriPermissions.valueAt(i);
-                Iterator<UriPermission> it = perms.values().iterator();
-            toploop:
-                while (it.hasNext()) {
-                    UriPermission perm = it.next();
-                    Uri targetUri = perm.uri;
-                    if (!authority.equals(targetUri.getAuthority())) {
-                        continue;
-                    }
-                    List<String> targetSegments = targetUri.getPathSegments();
-                    if (targetSegments == null) {
-                        continue;
-                    }
-                    if (targetSegments.size() < NS) {
-                        continue;
-                    }
-                    for (int j=0; j<NS; j++) {
-                        if (!SEGMENTS.get(j).equals(targetSegments.get(j))) {
-                            continue toploop;
-                        }
-                    }
-                    if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                            "Revoking " + perm.targetUid + " permission to " + perm.uri);
-                    persistChanged |= perm.clearModes(modeFlags, true);
+        int N = mGrantedUriPermissions.size();
+        for (int i = 0; i < N; i++) {
+            final int targetUid = mGrantedUriPermissions.keyAt(i);
+            final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+
+            for (Iterator<UriPermission> it = perms.values().iterator(); it.hasNext();) {
+                final UriPermission perm = it.next();
+                if (perm.uri.uri.isPathPrefixMatch(uri)) {
+                    if (DEBUG_URI_PERMISSION)
+                        Slog.v(TAG,
+                                "Revoking " + perm.targetUid + " permission to " + perm.uri);
+                    persistChanged |= perm.revokeModes(
+                            modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
                     if (perm.modeFlags == 0) {
                         it.remove();
                     }
                 }
-                if (perms.size() == 0) {
-                    mGrantedUriPermissions.remove(
-                            mGrantedUriPermissions.keyAt(i));
-                    N--;
-                    i--;
-                }
+            }
+
+            if (perms.isEmpty()) {
+                mGrantedUriPermissions.remove(targetUid);
+                N--;
+                i--;
             }
         }
 
@@ -6385,7 +6415,7 @@
 
     @Override
     public void revokeUriPermission(IApplicationThread caller, Uri uri,
-            int modeFlags) {
+            final int modeFlags) {
         enforceNotIsolatedCaller("revokeUriPermission");
         synchronized(this) {
             final ProcessRecord r = getRecordForAppLocked(caller);
@@ -6399,9 +6429,7 @@
                 return;
             }
 
-            modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
-                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-            if (modeFlags == 0) {
+            if (!Intent.isAccessUriMode(modeFlags)) {
                 return;
             }
 
@@ -6436,20 +6464,22 @@
 
         boolean persistChanged = false;
 
-        final int size = mGrantedUriPermissions.size();
-        for (int i = 0; i < size; i++) {
+        int N = mGrantedUriPermissions.size();
+        for (int i = 0; i < N; i++) {
+            final int targetUid = mGrantedUriPermissions.keyAt(i);
+            final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+
             // Only inspect grants matching user
             if (userHandle == UserHandle.USER_ALL
-                    || userHandle == UserHandle.getUserId(mGrantedUriPermissions.keyAt(i))) {
-                final Iterator<UriPermission> it = mGrantedUriPermissions.valueAt(i)
-                        .values().iterator();
-                while (it.hasNext()) {
+                    || userHandle == UserHandle.getUserId(targetUid)) {
+                for (Iterator<UriPermission> it = perms.values().iterator(); it.hasNext();) {
                     final UriPermission perm = it.next();
 
                     // Only inspect grants matching package
                     if (packageName == null || perm.sourcePkg.equals(packageName)
                             || perm.targetPkg.equals(packageName)) {
-                        persistChanged |= perm.clearModes(~0, persistable);
+                        persistChanged |= perm.revokeModes(
+                                persistable ? ~0 : ~Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
 
                         // Only remove when no modes remain; any persisted grants
                         // will keep this alive.
@@ -6458,6 +6488,12 @@
                         }
                     }
                 }
+
+                if (perms.isEmpty()) {
+                    mGrantedUriPermissions.remove(targetUid);
+                    N--;
+                    i--;
+                }
             }
         }
 
@@ -6477,7 +6513,7 @@
 
     @Override
     public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg,
-            Uri uri, int modeFlags) {
+            Uri uri, final int modeFlags) {
         synchronized(this) {
             UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
             if (owner == null) {
@@ -6531,8 +6567,9 @@
         ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList();
         synchronized (this) {
             final int size = mGrantedUriPermissions.size();
-            for (int i = 0 ; i < size; i++) {
-                for (UriPermission perm : mGrantedUriPermissions.valueAt(i).values()) {
+            for (int i = 0; i < size; i++) {
+                final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+                for (UriPermission perm : perms.values()) {
                     if (perm.persistedModeFlags != 0) {
                         persist.add(perm.snapshot());
                     }
@@ -6553,7 +6590,8 @@
                 writeIntAttribute(out, ATTR_USER_HANDLE, perm.userHandle);
                 out.attribute(null, ATTR_SOURCE_PKG, perm.sourcePkg);
                 out.attribute(null, ATTR_TARGET_PKG, perm.targetPkg);
-                out.attribute(null, ATTR_URI, String.valueOf(perm.uri));
+                out.attribute(null, ATTR_URI, String.valueOf(perm.uri.uri));
+                writeBooleanAttribute(out, ATTR_PREFIX, perm.uri.prefix);
                 writeIntAttribute(out, ATTR_MODE_FLAGS, perm.persistedModeFlags);
                 writeLongAttribute(out, ATTR_CREATED_TIME, perm.persistedCreateTime);
                 out.endTag(null, TAG_URI_GRANT);
@@ -6589,6 +6627,7 @@
                         final String sourcePkg = in.getAttributeValue(null, ATTR_SOURCE_PKG);
                         final String targetPkg = in.getAttributeValue(null, ATTR_TARGET_PKG);
                         final Uri uri = Uri.parse(in.getAttributeValue(null, ATTR_URI));
+                        final boolean prefix = readBooleanAttribute(in, ATTR_PREFIX);
                         final int modeFlags = readIntAttribute(in, ATTR_MODE_FLAGS);
                         final long createdTime = readLongAttribute(in, ATTR_CREATED_TIME, now);
 
@@ -6604,7 +6643,7 @@
                             }
                             if (targetUid != -1) {
                                 final UriPermission perm = findOrCreateUriPermissionLocked(
-                                        sourcePkg, targetPkg, targetUid, uri);
+                                        sourcePkg, targetPkg, targetUid, new GrantUri(uri, prefix));
                                 perm.initPersistedModes(modeFlags, createdTime);
                             }
                         } else {
@@ -6626,7 +6665,7 @@
     }
 
     @Override
-    public void takePersistableUriPermission(Uri uri, int modeFlags) {
+    public void takePersistableUriPermission(Uri uri, final int modeFlags) {
         enforceNotIsolatedCaller("takePersistableUriPermission");
 
         Preconditions.checkFlagsArgument(modeFlags,
@@ -6634,13 +6673,28 @@
 
         synchronized (this) {
             final int callingUid = Binder.getCallingUid();
-            final UriPermission perm = findUriPermissionLocked(callingUid, uri);
-            if (perm == null) {
-                throw new SecurityException("No permission grant found for UID " + callingUid
-                        + " and Uri " + uri.toSafeString());
+            boolean persistChanged = false;
+
+            UriPermission exactPerm = findUriPermissionLocked(callingUid, new GrantUri(uri, false));
+            UriPermission prefixPerm = findUriPermissionLocked(callingUid, new GrantUri(uri, true));
+
+            final boolean exactValid = (exactPerm != null)
+                    && ((modeFlags & exactPerm.persistableModeFlags) == modeFlags);
+            final boolean prefixValid = (prefixPerm != null)
+                    && ((modeFlags & prefixPerm.persistableModeFlags) == modeFlags);
+
+            if (!(exactValid || prefixValid)) {
+                throw new SecurityException("No persistable permission grants found for UID "
+                        + callingUid + " and Uri " + uri.toSafeString());
             }
 
-            boolean persistChanged = perm.takePersistableModes(modeFlags);
+            if (exactValid) {
+                persistChanged |= exactPerm.takePersistableModes(modeFlags);
+            }
+            if (prefixValid) {
+                persistChanged |= prefixPerm.takePersistableModes(modeFlags);
+            }
+
             persistChanged |= maybePrunePersistedUriGrantsLocked(callingUid);
 
             if (persistChanged) {
@@ -6650,7 +6704,7 @@
     }
 
     @Override
-    public void releasePersistableUriPermission(Uri uri, int modeFlags) {
+    public void releasePersistableUriPermission(Uri uri, final int modeFlags) {
         enforceNotIsolatedCaller("releasePersistableUriPermission");
 
         Preconditions.checkFlagsArgument(modeFlags,
@@ -6658,16 +6712,24 @@
 
         synchronized (this) {
             final int callingUid = Binder.getCallingUid();
+            boolean persistChanged = false;
 
-            final UriPermission perm = findUriPermissionLocked(callingUid, uri);
-            if (perm == null) {
-                Slog.w(TAG, "No permission grant found for UID " + callingUid + " and Uri "
-                        + uri.toSafeString());
-                return;
+            UriPermission exactPerm = findUriPermissionLocked(callingUid, new GrantUri(uri, false));
+            UriPermission prefixPerm = findUriPermissionLocked(callingUid, new GrantUri(uri, true));
+            if (exactPerm == null && prefixPerm == null) {
+                throw new SecurityException("No permission grants found for UID " + callingUid
+                        + " and Uri " + uri.toSafeString());
             }
 
-            final boolean persistChanged = perm.releasePersistableModes(modeFlags);
-            removeUriPermissionIfNeededLocked(perm);
+            if (exactPerm != null) {
+                persistChanged |= exactPerm.releasePersistableModes(modeFlags);
+                removeUriPermissionIfNeededLocked(exactPerm);
+            }
+            if (prefixPerm != null) {
+                persistChanged |= prefixPerm.releasePersistableModes(modeFlags);
+                removeUriPermissionIfNeededLocked(prefixPerm);
+            }
+
             if (persistChanged) {
                 schedulePersistUriGrants();
             }
@@ -6681,7 +6743,7 @@
      * @return if any mutations occured that require persisting.
      */
     private boolean maybePrunePersistedUriGrantsLocked(int uid) {
-        final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid);
+        final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid);
         if (perms == null) return false;
         if (perms.size() < MAX_PERSISTED_URI_GRANTS) return false;
 
@@ -6731,13 +6793,12 @@
         final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
         synchronized (this) {
             if (incoming) {
-                final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+                final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(
+                        callingUid);
                 if (perms == null) {
                     Slog.w(TAG, "No permission grants found for " + packageName);
                 } else {
-                    final int size = perms.size();
-                    for (int i = 0; i < size; i++) {
-                        final UriPermission perm = perms.valueAt(i);
+                    for (UriPermission perm : perms.values()) {
                         if (packageName.equals(perm.targetPkg) && perm.persistedModeFlags != 0) {
                             result.add(perm.buildPersistedPublicApiObject());
                         }
@@ -6746,10 +6807,9 @@
             } else {
                 final int size = mGrantedUriPermissions.size();
                 for (int i = 0; i < size; i++) {
-                    final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
-                    final int permsSize = perms.size();
-                    for (int j = 0; j < permsSize; j++) {
-                        final UriPermission perm = perms.valueAt(j);
+                    final ArrayMap<GrantUri, UriPermission> perms =
+                            mGrantedUriPermissions.valueAt(i);
+                    for (UriPermission perm : perms.values()) {
                         if (packageName.equals(perm.sourcePkg) && perm.persistedModeFlags != 0) {
                             result.add(perm.buildPersistedPublicApiObject());
                         }
@@ -7619,11 +7679,11 @@
                 }
             }
         }
-        
-        ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+
+        final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
         if (perms != null) {
-            for (Map.Entry<Uri, UriPermission> uri : perms.entrySet()) {
-                if (uri.getKey().getAuthority().equals(cpi.authority)) {
+            for (GrantUri uri : perms.keySet()) {
+                if (uri.uri.getAuthority().equals(cpi.authority)) {
                     return null;
                 }
             }
@@ -11590,8 +11650,7 @@
                 if (dumpUid >= -1 && UserHandle.getAppId(uid) != dumpUid) {
                     continue;
                 }
-                ArrayMap<Uri, UriPermission> perms
-                        = mGrantedUriPermissions.valueAt(i);
+                final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
                 if (!printed) {
                     if (needSep) pw.println();
                     needSep = true;
@@ -11599,8 +11658,7 @@
                     printed = true;
                     printedAnything = true;
                 }
-                pw.print("  * UID "); pw.print(uid);
-                        pw.println(" holds:");
+                pw.print("  * UID "); pw.print(uid); pw.println(" holds:");
                 for (UriPermission perm : perms.values()) {
                     pw.print("    "); pw.println(perm);
                     if (dumpAll) {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 60adfb0..3e59def 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -215,14 +215,7 @@
             pw.print(prefix); pw.print("pendingOptions="); pw.println(pendingOptions);
         }
         if (uriPermissions != null) {
-            if (uriPermissions.readUriPermissions != null) {
-                pw.print(prefix); pw.print("readUriPermissions=");
-                        pw.println(uriPermissions.readUriPermissions);
-            }
-            if (uriPermissions.writeUriPermissions != null) {
-                pw.print(prefix); pw.print("writeUriPermissions=");
-                        pw.println(uriPermissions.writeUriPermissions);
-            }
+            uriPermissions.dump(pw, prefix);
         }
         pw.print(prefix); pw.print("launchFailed="); pw.print(launchFailed);
                 pw.print(" launchCount="); pw.print(launchCount);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 111f010..afef4ff 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2999,7 +2999,7 @@
         }
     }
 
-    class ActivityContainer extends IActivityContainer.Stub {
+    class ActivityContainer extends android.app.IActivityContainer.Stub {
         final int mStackId;
         IActivityContainerCallback mCallback = null;
         final ActivityStack mStack;
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index b12843b..443218c 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -29,6 +29,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
 import java.net.InetSocketAddress;
 import java.net.InetUnixAddress;
 
@@ -178,7 +179,7 @@
     }
 
     static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes)
-            throws ErrnoException {
+            throws ErrnoException, InterruptedIOException {
         int totalRead = 0;
         while (numBytes > 0) {
             int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 363a9b7..e54c95e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -193,14 +193,7 @@
                         pw.println(si.neededGrants);
             }
             if (si.uriPermissions != null) {
-                if (si.uriPermissions.readUriPermissions != null) {
-                    pw.print(prefix); pw.print("  readUriPermissions=");
-                            pw.println(si.uriPermissions.readUriPermissions);
-                }
-                if (si.uriPermissions.writeUriPermissions != null) {
-                    pw.print(prefix); pw.print("  writeUriPermissions=");
-                            pw.println(si.uriPermissions.writeUriPermissions);
-                }
+                si.uriPermissions.dump(pw, prefix);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/UriPermission.java b/services/core/java/com/android/server/am/UriPermission.java
index 1f12b74..4970b8d 100644
--- a/services/core/java/com/android/server/am/UriPermission.java
+++ b/services/core/java/com/android/server/am/UriPermission.java
@@ -17,15 +17,16 @@
 package com.android.server.am;
 
 import android.content.Intent;
-import android.net.Uri;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
 
+import com.android.server.am.ActivityManagerService.GrantUri;
 import com.google.android.collect.Sets;
 
 import java.io.PrintWriter;
 import java.util.Comparator;
-import java.util.HashSet;
 
 /**
  * Description of a permission granted to an app to access a particular URI.
@@ -50,7 +51,7 @@
     /** Cached UID of {@link #targetPkg}; should not be persisted */
     final int targetUid;
 
-    final Uri uri;
+    final GrantUri uri;
 
     /**
      * Allowed modes. All permission enforcement should use this field. Must
@@ -61,12 +62,13 @@
      */
     int modeFlags = 0;
 
-    /** Allowed modes with explicit owner. */
+    /** Allowed modes with active owner. */
     int ownedModeFlags = 0;
     /** Allowed modes without explicit owner. */
     int globalModeFlags = 0;
     /** Allowed modes that have been offered for possible persisting. */
     int persistableModeFlags = 0;
+
     /** Allowed modes that should be persisted across device boots. */
     int persistedModeFlags = 0;
 
@@ -78,12 +80,12 @@
 
     private static final long INVALID_TIME = Long.MIN_VALUE;
 
-    private HashSet<UriPermissionOwner> mReadOwners;
-    private HashSet<UriPermissionOwner> mWriteOwners;
+    private ArraySet<UriPermissionOwner> mReadOwners;
+    private ArraySet<UriPermissionOwner> mWriteOwners;
 
     private String stringName;
 
-    UriPermission(String sourcePkg, String targetPkg, int targetUid, Uri uri) {
+    UriPermission(String sourcePkg, String targetPkg, int targetUid, GrantUri uri) {
         this.userHandle = UserHandle.getUserId(targetUid);
         this.sourcePkg = sourcePkg;
         this.targetPkg = targetPkg;
@@ -100,6 +102,9 @@
      * global or owner grants.
      */
     void initPersistedModes(int modeFlags, long createdTime) {
+        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
         persistableModeFlags = modeFlags;
         persistedModeFlags = modeFlags;
         persistedCreateTime = createdTime;
@@ -107,7 +112,11 @@
         updateModeFlags();
     }
 
-    void grantModes(int modeFlags, boolean persistable, UriPermissionOwner owner) {
+    void grantModes(int modeFlags, UriPermissionOwner owner) {
+        final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
         if (persistable) {
             persistableModeFlags |= modeFlags;
         }
@@ -130,10 +139,14 @@
      * @return if mode changes should trigger persisting.
      */
     boolean takePersistableModes(int modeFlags) {
+        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
         if ((modeFlags & persistableModeFlags) != modeFlags) {
-            throw new SecurityException("Requested flags 0x"
+            Slog.w(TAG, "Requested flags 0x"
                     + Integer.toHexString(modeFlags) + ", but only 0x"
                     + Integer.toHexString(persistableModeFlags) + " are allowed");
+            return false;
         }
 
         final int before = persistedModeFlags;
@@ -148,6 +161,9 @@
     }
 
     boolean releasePersistableModes(int modeFlags) {
+        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
         final int before = persistedModeFlags;
 
         persistableModeFlags &= ~modeFlags;
@@ -164,7 +180,11 @@
     /**
      * @return if mode changes should trigger persisting.
      */
-    boolean clearModes(int modeFlags, boolean persistable) {
+    boolean revokeModes(int modeFlags) {
+        final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
         final int before = persistedModeFlags;
 
         if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
@@ -208,6 +228,8 @@
      * Return strength of this permission grant for the given flags.
      */
     public int getStrength(int modeFlags) {
+        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
         if ((persistableModeFlags & modeFlags) == modeFlags) {
             return STRENGTH_PERSISTABLE;
         } else if ((globalModeFlags & modeFlags) == modeFlags) {
@@ -221,7 +243,7 @@
 
     private void addReadOwner(UriPermissionOwner owner) {
         if (mReadOwners == null) {
-            mReadOwners = Sets.newHashSet();
+            mReadOwners = Sets.newArraySet();
             ownedModeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
             updateModeFlags();
         }
@@ -246,7 +268,7 @@
 
     private void addWriteOwner(UriPermissionOwner owner) {
         if (mWriteOwners == null) {
-            mWriteOwners = Sets.newHashSet();
+            mWriteOwners = Sets.newArraySet();
             ownedModeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
             updateModeFlags();
         }
@@ -333,7 +355,7 @@
         final int userHandle;
         final String sourcePkg;
         final String targetPkg;
-        final Uri uri;
+        final GrantUri uri;
         final int persistedModeFlags;
         final long persistedCreateTime;
 
@@ -352,6 +374,6 @@
     }
 
     public android.content.UriPermission buildPersistedPublicApiObject() {
-        return new android.content.UriPermission(uri, persistedModeFlags, persistedCreateTime);
+        return new android.content.UriPermission(uri.uri, persistedModeFlags, persistedCreateTime);
     }
 }
diff --git a/services/core/java/com/android/server/am/UriPermissionOwner.java b/services/core/java/com/android/server/am/UriPermissionOwner.java
index 7bbd3bc..65d7047 100644
--- a/services/core/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/am/UriPermissionOwner.java
@@ -20,8 +20,11 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
+import android.util.ArraySet;
 
-import java.util.HashSet;
+import com.google.android.collect.Sets;
+
+import java.io.PrintWriter;
 import java.util.Iterator;
 
 final class UriPermissionOwner {
@@ -30,8 +33,8 @@
 
     Binder externalToken;
 
-    HashSet<UriPermission> readUriPermissions; // special access to reading uris.
-    HashSet<UriPermission> writeUriPermissions; // special access to writing uris.
+    private ArraySet<UriPermission> mReadPerms;
+    private ArraySet<UriPermission> mWritePerms;
 
     class ExternalToken extends Binder {
         UriPermissionOwner getOwner() {
@@ -39,9 +42,9 @@
         }
     }
 
-    UriPermissionOwner(ActivityManagerService _service, Object _owner) {
-        service = _service;
-        owner = _owner;
+    UriPermissionOwner(ActivityManagerService service, Object owner) {
+        this.service = service;
+        this.owner = owner;
     }
 
     Binder getExternalTokenLocked() {
@@ -64,82 +67,76 @@
     }
 
     void removeUriPermissionsLocked(int mode) {
-        if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
-                && readUriPermissions != null) {
-            for (UriPermission perm : readUriPermissions) {
-                perm.removeReadOwner(this);
-                service.removeUriPermissionIfNeededLocked(perm);
-            }
-            readUriPermissions = null;
-        }
-        if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
-                && writeUriPermissions != null) {
-            for (UriPermission perm : writeUriPermissions) {
-                perm.removeWriteOwner(this);
-                service.removeUriPermissionIfNeededLocked(perm);
-            }
-            writeUriPermissions = null;
-        }
+        removeUriPermissionLocked(null, mode);
     }
 
     void removeUriPermissionLocked(Uri uri, int mode) {
-        if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
-                && readUriPermissions != null) {
-            Iterator<UriPermission> it = readUriPermissions.iterator();
+        if ((mode & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
+                && mReadPerms != null) {
+            Iterator<UriPermission> it = mReadPerms.iterator();
             while (it.hasNext()) {
                 UriPermission perm = it.next();
-                if (uri.equals(perm.uri)) {
+                if (uri == null || uri.equals(perm.uri)) {
                     perm.removeReadOwner(this);
                     service.removeUriPermissionIfNeededLocked(perm);
                     it.remove();
                 }
             }
-            if (readUriPermissions.size() == 0) {
-                readUriPermissions = null;
+            if (mReadPerms.isEmpty()) {
+                mReadPerms = null;
             }
         }
-        if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
-                && writeUriPermissions != null) {
-            Iterator<UriPermission> it = writeUriPermissions.iterator();
+        if ((mode & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
+                && mWritePerms != null) {
+            Iterator<UriPermission> it = mWritePerms.iterator();
             while (it.hasNext()) {
                 UriPermission perm = it.next();
-                if (uri.equals(perm.uri)) {
+                if (uri == null || uri.equals(perm.uri)) {
                     perm.removeWriteOwner(this);
                     service.removeUriPermissionIfNeededLocked(perm);
                     it.remove();
                 }
             }
-            if (writeUriPermissions.size() == 0) {
-                writeUriPermissions = null;
+            if (mWritePerms.isEmpty()) {
+                mWritePerms = null;
             }
         }
     }
 
     public void addReadPermission(UriPermission perm) {
-        if (readUriPermissions == null) {
-            readUriPermissions = new HashSet<UriPermission>();
+        if (mReadPerms == null) {
+            mReadPerms = Sets.newArraySet();
         }
-        readUriPermissions.add(perm);
+        mReadPerms.add(perm);
     }
 
     public void addWritePermission(UriPermission perm) {
-        if (writeUriPermissions == null) {
-            writeUriPermissions = new HashSet<UriPermission>();
+        if (mWritePerms == null) {
+            mWritePerms = Sets.newArraySet();
         }
-        writeUriPermissions.add(perm);
+        mWritePerms.add(perm);
     }
 
     public void removeReadPermission(UriPermission perm) {
-        readUriPermissions.remove(perm);
-        if (readUriPermissions.size() == 0) {
-            readUriPermissions = null;
+        mReadPerms.remove(perm);
+        if (mReadPerms.isEmpty()) {
+            mReadPerms = null;
         }
     }
 
     public void removeWritePermission(UriPermission perm) {
-        writeUriPermissions.remove(perm);
-        if (writeUriPermissions.size() == 0) {
-            writeUriPermissions = null;
+        mWritePerms.remove(perm);
+        if (mWritePerms.isEmpty()) {
+            mWritePerms = null;
+        }
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        if (mReadPerms != null) {
+            pw.print(prefix); pw.print("readUriPermissions="); pw.println(mReadPerms);
+        }
+        if (mWritePerms != null) {
+            pw.print(prefix); pw.print("writeUriPermissions="); pw.println(mWritePerms);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2c8f1b4..91afec7 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -95,6 +95,14 @@
         }
     }
 
+    static boolean shouldBlank(int state) {
+        return state == Display.STATE_OFF;
+    }
+
+    static boolean shouldUnblank(int state) {
+        return state == Display.STATE_ON || state == Display.STATE_DOZING;
+    }
+
     private final class LocalDisplayDevice extends DisplayDevice {
         private final int mBuiltInDisplayId;
         private final SurfaceControl.PhysicalDisplayInfo mPhys;
@@ -180,9 +188,9 @@
         @Override
         public void requestDisplayStateLocked(int state) {
             if (mState != state) {
-                if (state == Display.STATE_OFF && mState != Display.STATE_OFF) {
+                if (shouldBlank(state) && !shouldBlank(mState)) {
                     SurfaceControl.blankDisplay(getDisplayTokenLocked());
-                } else if (state != Display.STATE_OFF && mState == Display.STATE_OFF) {
+                } else if (shouldUnblank(state) && !shouldUnblank(mState)) {
                     SurfaceControl.unblankDisplay(getDisplayTokenLocked());
                 }
                 mState = state;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a6e83a7..53db9ef 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManager;
 import android.app.IStopUserCallback;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -437,7 +438,7 @@
 
     @Override
     public void setUserRestrictions(Bundle restrictions, int userId) {
-        checkManageUsersPermission("setUserRestrictions");
+        checkProfileOwnerOrManageUsersPermission("setUserRestrictions");
         if (restrictions == null) return;
 
         synchronized (mPackagesLock) {
@@ -463,16 +464,53 @@
      * @param message used as message if SecurityException is thrown
      * @throws SecurityException if the caller is not system or root
      */
-    private static final void checkManageUsersPermission(String message) {
+    private final void checkManageUsersPermission(String message) {
         final int uid = Binder.getCallingUid();
-        if (uid != Process.SYSTEM_UID && uid != 0
-                && ActivityManager.checkComponentPermission(
-                        android.Manifest.permission.MANAGE_USERS,
-                        uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+
+        if (missingManageUsersPermission(uid)) {
             throw new SecurityException("You need MANAGE_USERS permission to: " + message);
         }
     }
 
+    /**
+     * Enforces that only the system UID, root's UID, apps that have the
+     * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS}
+     * permission, the profile owner, or the device owner can make certain calls to the
+     * UserManager.
+     *
+     * @param message used as message if SecurityException is thrown
+     * @throws SecurityException if the caller is not system, root, or device
+     * owner
+     */
+    private final void checkProfileOwnerOrManageUsersPermission(String message) {
+        final int uid = Binder.getCallingUid();
+        boolean isProfileOwner = false;
+        if (mContext != null && mContext.getPackageManager() != null) {
+            String[] pkgs = mContext.getPackageManager().getPackagesForUid(uid);
+            DevicePolicyManager dpm =
+                    (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+            if (dpm != null) {
+                for (String pkg : pkgs) {
+                    if (dpm.isDeviceOwnerApp(pkg) || dpm.isProfileOwnerApp(pkg)) {
+                        isProfileOwner = true;
+                    }
+                }
+            }
+        }
+
+        if (missingManageUsersPermission(uid) && !isProfileOwner) {
+            throw new SecurityException(
+                    "You need MANAGE_USERS permission or device owner privileges to: " + message);
+        }
+    }
+
+    private boolean missingManageUsersPermission(int uid) {
+        return uid != Process.SYSTEM_UID && uid != 0
+                && ActivityManager.checkComponentPermission(
+                        android.Manifest.permission.MANAGE_USERS,
+                        uid, -1, true) != PackageManager.PERMISSION_GRANTED;
+    }
+
     private void writeBitmapLocked(UserInfo info, Bitmap bitmap) {
         try {
             File dir = new File(mUsersDir, Integer.toString(info.id));
@@ -1175,7 +1213,8 @@
     public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
         if (UserHandle.getCallingUserId() != userId
                 || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
-            checkManageUsersPermission("Only system can get restrictions for other users/apps");
+            checkProfileOwnerOrManageUsersPermission(
+                    "Only system or device owner can get restrictions for other users/apps");
         }
         synchronized (mPackagesLock) {
             // Read the restrictions from XML
@@ -1188,7 +1227,8 @@
             int userId) {
         if (UserHandle.getCallingUserId() != userId
                 || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
-            checkManageUsersPermission("Only system can set restrictions for other users/apps");
+            checkProfileOwnerOrManageUsersPermission(
+                    "Only system or device owner can set restrictions for other users/apps");
         }
         synchronized (mPackagesLock) {
             // Write the restrictions to XML
@@ -1290,7 +1330,8 @@
 
     @Override
     public void removeRestrictions() {
-        checkManageUsersPermission("Only system can remove restrictions");
+        checkProfileOwnerOrManageUsersPermission(
+                "Only system or device owner can remove restrictions");
         final int userHandle = UserHandle.getCallingUserId();
         removeRestrictionsForUser(userHandle, true);
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 82cef7f..cfe24fe 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -168,6 +168,10 @@
     // Poll interval in milliseconds for watching boot animation finished.
     private static final int BOOT_ANIMATION_POLL_INTERVAL = 200;
 
+    //powerHint
+    private static final int POWER_HINT_LOW_POWER_MODE = 5;
+    private static boolean mLowPowerModeEnabled;
+
     private final Context mContext;
     private LightsManager mLightsManager;
     private BatteryService mBatteryService;
@@ -530,7 +534,9 @@
             resolver.registerContentObserver(Settings.System.getUriFor(
                     Settings.System.SCREEN_BRIGHTNESS_MODE),
                     false, mSettingsObserver, UserHandle.USER_ALL);
-
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.LOW_POWER_MODE),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
             // Go.
             readConfigurationLocked();
             updateSettingsLocked();
@@ -610,6 +616,14 @@
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
 
+        boolean mIsEnabled = Settings.Global.getInt(resolver,
+            Settings.Global.LOW_POWER_MODE, 0) != 0;
+        if (mIsEnabled != mLowPowerModeEnabled) {
+            BinderService bs = new BinderService();
+            bs.powerHint(POWER_HINT_LOW_POWER_MODE, mIsEnabled ? 1 : 0);
+            mLowPowerModeEnabled = mIsEnabled;
+        }
+
         mDirty |= DIRTY_SETTINGS;
     }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
index ebfe9bc..8862f5b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
@@ -355,227 +355,162 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_setConcat(long native_object, long a, long b) {
+    /*package*/ static void native_setConcat(long native_object, long a, long b) {
         if (a == native_object) {
-            return native_preConcat(native_object, b);
+            native_preConcat(native_object, b);
+            return;
         } else if (b == native_object) {
-            return native_postConcat(native_object, a);
+            native_postConcat(native_object, a);
+            return;
         }
 
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
-        }
-
         Matrix_Delegate a_mtx = sManager.getDelegate(a);
-        if (a_mtx == null) {
-            return false;
-        }
-
         Matrix_Delegate b_mtx = sManager.getDelegate(b);
-        if (b_mtx == null) {
-            return false;
+        if (d != null && a_mtx != null && b_mtx != null) {
+            multiply(d.mValues, a_mtx.mValues, b_mtx.mValues);
         }
-
-        multiply(d.mValues, a_mtx.mValues, b_mtx.mValues);
-
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_preTranslate(long native_object, float dx, float dy) {
+    /*package*/ static void native_preTranslate(long native_object, float dx, float dy) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.preTransform(getTranslate(dx, dy));
         }
-
-        d.preTransform(getTranslate(dx, dy));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_preScale(long native_object, float sx, float sy,
+    /*package*/ static void native_preScale(long native_object, float sx, float sy,
             float px, float py) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.preTransform(getScale(sx, sy, px, py));
         }
-
-        d.preTransform(getScale(sx, sy, px, py));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_preScale(long native_object, float sx, float sy) {
+    /*package*/ static void native_preScale(long native_object, float sx, float sy) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.preTransform(getScale(sx, sy));
         }
-
-        d.preTransform(getScale(sx, sy));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_preRotate(long native_object, float degrees,
+    /*package*/ static void native_preRotate(long native_object, float degrees,
             float px, float py) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.preTransform(getRotate(degrees, px, py));
         }
-
-        d.preTransform(getRotate(degrees, px, py));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_preRotate(long native_object, float degrees) {
+    /*package*/ static void native_preRotate(long native_object, float degrees) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+
+            double rad = Math.toRadians(degrees);
+            float sin = (float) Math.sin(rad);
+            float cos = (float) Math.cos(rad);
+
+            d.preTransform(getRotate(sin, cos));
         }
-
-        double rad = Math.toRadians(degrees);
-        float sin = (float)Math.sin(rad);
-        float cos = (float)Math.cos(rad);
-
-        d.preTransform(getRotate(sin, cos));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_preSkew(long native_object, float kx, float ky,
+    /*package*/ static void native_preSkew(long native_object, float kx, float ky,
             float px, float py) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.preTransform(getSkew(kx, ky, px, py));
         }
-
-        d.preTransform(getSkew(kx, ky, px, py));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_preSkew(long native_object, float kx, float ky) {
+    /*package*/ static void native_preSkew(long native_object, float kx, float ky) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.preTransform(getSkew(kx, ky));
         }
-
-        d.preTransform(getSkew(kx, ky));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_preConcat(long native_object, long other_matrix) {
+    /*package*/ static void native_preConcat(long native_object, long other_matrix) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
-        }
-
         Matrix_Delegate other = sManager.getDelegate(other_matrix);
-        if (other == null) {
-            return false;
+        if (d != null && other != null) {
+            d.preTransform(other.mValues);
         }
-
-        d.preTransform(other.mValues);
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_postTranslate(long native_object, float dx, float dy) {
+    /*package*/ static void native_postTranslate(long native_object, float dx, float dy) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.postTransform(getTranslate(dx, dy));
         }
-
-        d.postTransform(getTranslate(dx, dy));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_postScale(long native_object, float sx, float sy,
+    /*package*/ static void native_postScale(long native_object, float sx, float sy,
             float px, float py) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.postTransform(getScale(sx, sy, px, py));
         }
-
-        d.postTransform(getScale(sx, sy, px, py));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_postScale(long native_object, float sx, float sy) {
+    /*package*/ static void native_postScale(long native_object, float sx, float sy) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.postTransform(getScale(sx, sy));
         }
-
-        d.postTransform(getScale(sx, sy));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_postRotate(long native_object, float degrees,
+    /*package*/ static void native_postRotate(long native_object, float degrees,
             float px, float py) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.postTransform(getRotate(degrees, px, py));
         }
-
-        d.postTransform(getRotate(degrees, px, py));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_postRotate(long native_object, float degrees) {
+    /*package*/ static void native_postRotate(long native_object, float degrees) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.postTransform(getRotate(degrees));
         }
-
-        d.postTransform(getRotate(degrees));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_postSkew(long native_object, float kx, float ky,
+    /*package*/ static void native_postSkew(long native_object, float kx, float ky,
             float px, float py) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.postTransform(getSkew(kx, ky, px, py));
         }
-
-        d.postTransform(getSkew(kx, ky, px, py));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_postSkew(long native_object, float kx, float ky) {
+    /*package*/ static void native_postSkew(long native_object, float kx, float ky) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
+        if (d != null) {
+            d.postTransform(getSkew(kx, ky));
         }
-
-        d.postTransform(getSkew(kx, ky));
-        return true;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_postConcat(long native_object, long other_matrix) {
+    /*package*/ static void native_postConcat(long native_object, long other_matrix) {
         Matrix_Delegate d = sManager.getDelegate(native_object);
-        if (d == null) {
-            return false;
-        }
-
         Matrix_Delegate other = sManager.getDelegate(other_matrix);
-        if (other == null) {
-            return false;
+        if (d != null && other != null) {
+            d.postTransform(other.mValues);
         }
-
-        d.postTransform(other.mValues);
-        return true;
     }
 
     @LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index cdbe200..af22f44 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -32,10 +32,6 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.InflateException;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
 
 import java.io.File;
 
@@ -155,6 +151,9 @@
     @Override
     public View inflate(int resource, ViewGroup root) {
         Context context = getContext();
+        if (context instanceof ContextThemeWrapper) {
+            context = ((ContextThemeWrapper) context).getBaseContext();
+        }
         if (context instanceof BridgeContext) {
             BridgeContext bridgeContext = (BridgeContext)context;
 
@@ -217,43 +216,16 @@
     }
 
     private void setupViewInContext(View view, AttributeSet attrs) {
-        if (getContext() instanceof BridgeContext) {
-            BridgeContext bc = (BridgeContext) getContext();
-            if (attrs instanceof BridgeXmlBlockParser) {
-                BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs;
-
-                // get the view key
-                Object viewKey = parser.getViewCookie();
-
-                if (viewKey == null) {
-                    int currentDepth = parser.getDepth();
-
-                    // test whether we are in an included file or in a adapter binding view.
-                    BridgeXmlBlockParser previousParser = bc.getPreviousParser();
-                    if (previousParser != null) {
-                        // looks like we inside an embedded layout.
-                        // only apply the cookie of the calling node (<include>) if we are at the
-                        // top level of the embedded layout. If there is a merge tag, then
-                        // skip it and look for the 2nd level
-                        int testDepth = mIsInMerge ? 2 : 1;
-                        if (currentDepth == testDepth) {
-                            viewKey = previousParser.getViewCookie();
-                            // if we are in a merge, wrap the cookie in a MergeCookie.
-                            if (viewKey != null && mIsInMerge) {
-                                viewKey = new MergeCookie(viewKey);
-                            }
-                        }
-                    } else if (mResourceReference != null && currentDepth == 1) {
-                        // else if there's a resource reference, this means we are in an adapter
-                        // binding case. Set the resource ref as the view cookie only for the top
-                        // level view.
-                        viewKey = mResourceReference;
-                    }
-                }
-
-                if (viewKey != null) {
-                    bc.addViewKey(view, viewKey);
-                }
+        Context context = getContext();
+        if (context instanceof ContextThemeWrapper) {
+            context = ((ContextThemeWrapper) context).getBaseContext();
+        }
+        if (context instanceof BridgeContext) {
+            BridgeContext bc = (BridgeContext) context;
+            // get the view key
+            Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
+            if (viewKey != null) {
+                bc.addViewKey(view, viewKey);
             }
         }
     }
@@ -270,4 +242,44 @@
     public LayoutInflater cloneInContext(Context newContext) {
         return new BridgeInflater(this, newContext);
     }
+
+    /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc,
+            ResourceReference resourceReference, boolean isInMerge) {
+
+        if (!(attrs instanceof BridgeXmlBlockParser)) {
+            return null;
+        }
+        BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs);
+
+        // get the view key
+        Object viewKey = parser.getViewCookie();
+
+        if (viewKey == null) {
+            int currentDepth = parser.getDepth();
+
+            // test whether we are in an included file or in a adapter binding view.
+            BridgeXmlBlockParser previousParser = bc.getPreviousParser();
+            if (previousParser != null) {
+                // looks like we are inside an embedded layout.
+                // only apply the cookie of the calling node (<include>) if we are at the
+                // top level of the embedded layout. If there is a merge tag, then
+                // skip it and look for the 2nd level
+                int testDepth = isInMerge ? 2 : 1;
+                if (currentDepth == testDepth) {
+                    viewKey = previousParser.getViewCookie();
+                    // if we are in a merge, wrap the cookie in a MergeCookie.
+                    if (viewKey != null && isInMerge) {
+                        viewKey = new MergeCookie(viewKey);
+                    }
+                }
+            } else if (resourceReference != null && currentDepth == 1) {
+                // else if there's a resource reference, this means we are in an adapter
+                // binding case. Set the resource ref as the view cookie only for the top
+                // level view.
+                viewKey = resourceReference;
+            }
+        }
+
+        return viewKey;
+    }
 }
diff --git a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
new file mode 100644
index 0000000..22232f3
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.internal.view.menu.BridgeMenuItemImpl;
+import com.android.internal.view.menu.MenuView;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.AttributeSet;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link MenuInflater}
+ * <p/>
+ * Through the layoutlib_create tool, the original  methods of MenuInflater have been
+ * replaced by calls to methods of the same name in this delegate class.
+ * <p/>
+ * The main purpose of the class is to get the view key from the menu xml parser and add it to
+ * the menu item. The view key is used by the IDE to match the individual view elements to the
+ * corresponding xml tag in the menu/layout file.
+ * <p/>
+ * For Menus, the views may be reused and the {@link MenuItem} is a better object to hold the
+ * view key than the {@link MenuView.ItemView}. At the time of computation of the rest of {@link
+ * ViewInfo}, we check the corresponding view key in the menu item for the view and add it
+ */
+public class MenuInflater_Delegate {
+
+    @LayoutlibDelegate
+    /*package*/ static void emptyMethod(MenuInflater thisInflater, MenuItem menuItem,
+            AttributeSet attrs) {
+        if (menuItem instanceof BridgeMenuItemImpl) {
+            Context context = thisInflater.getContext();
+            if (context instanceof ContextThemeWrapper) {
+                context = ((ContextThemeWrapper) context).getBaseContext();
+            }
+            if (context instanceof BridgeContext) {
+                Object viewKey = BridgeInflater.getViewKeyFromParser(
+                        attrs, ((BridgeContext) context), null, false);
+                ((BridgeMenuItemImpl) menuItem).setViewCookie(viewKey);
+                return;
+            }
+        }
+        // This means that Bridge did not take over the instantiation of some object properly.
+        // This is most likely a bug in the LayoutLib code.
+        Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+                "Action Bar Menu rendering may be incorrect.", null);
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void emptyMethod(MenuInflater thisInflater, SubMenu subMenu,
+            AttributeSet parser) {
+        emptyMethod(thisInflater, subMenu.getItem(), parser);
+    }
+
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
new file mode 100644
index 0000000..4bef424
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 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.internal.view.menu;
+
+/**
+ * An extension of the {@link MenuItemImpl} to store the view cookie also.
+ */
+public class BridgeMenuItemImpl extends MenuItemImpl {
+
+    /**
+     * An object returned by the IDE that helps mapping each View to the corresponding XML tag in
+     * the layout. For Menus, we store this cookie here and attach it to the corresponding view
+     * at the time of rendering.
+     */
+    private Object viewCookie;
+
+    /**
+     * Instantiates this menu item.
+     */
+    BridgeMenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
+            CharSequence title, int showAsAction) {
+        super(menu, group, id, categoryOrder, ordering, title, showAsAction);
+    }
+
+
+    public Object getViewCookie() {
+        return viewCookie;
+    }
+
+    public void setViewCookie(Object viewCookie) {
+        this.viewCookie = viewCookie;
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java
new file mode 100644
index 0000000..505fb81
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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.internal.view.menu;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link MenuBuilder}
+ * <p/>
+ * Through the layoutlib_create tool, the original  methods of {@code MenuBuilder} have been
+ * replaced by calls to methods of the same name in this delegate class.
+ */
+public class MenuBuilder_Delegate {
+    /**
+     * The method overrides the instantiation of the {@link MenuItemImpl} with an instance of
+     * {@link BridgeMenuItemImpl} so that view cookies may be stored.
+     */
+    @LayoutlibDelegate
+    /*package*/ static MenuItemImpl createNewMenuItem(MenuBuilder thisMenu, int group, int id,
+            int categoryOrder, int ordering, CharSequence title, int defaultShowAsAction) {
+        return new BridgeMenuItemImpl(thisMenu, group, id, categoryOrder, ordering, title,
+                defaultShowAsAction);
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index afcadef..9787432 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -28,7 +28,6 @@
 import com.android.ide.common.rendering.api.IAnimationListener;
 import com.android.ide.common.rendering.api.ILayoutPullParser;
 import com.android.ide.common.rendering.api.IProjectCallback;
-import com.android.ide.common.rendering.api.RenderParams;
 import com.android.ide.common.rendering.api.RenderResources;
 import com.android.ide.common.rendering.api.RenderSession;
 import com.android.ide.common.rendering.api.ResourceReference;
@@ -39,6 +38,12 @@
 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
 import com.android.ide.common.rendering.api.ViewInfo;
 import com.android.internal.util.XmlUtils;
+import com.android.internal.view.menu.ActionMenuItemView;
+import com.android.internal.view.menu.BridgeMenuItemImpl;
+import com.android.internal.view.menu.IconMenuItemView;
+import com.android.internal.view.menu.ListMenuItemView;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuView;
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
@@ -101,11 +106,10 @@
 
 /**
  * Class implementing the render session.
- *
+ * <p/>
  * A session is a stateful representation of a layout file. It is initialized with data coming
  * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then
  * be done on the layout.
- *
  */
 public class RenderSessionImpl extends RenderAction<SessionParams> {
 
@@ -172,7 +176,7 @@
     @Override
     public Result init(long timeout) {
         Result result = super.init(timeout);
-        if (result.isSuccess() == false) {
+        if (!result.isSuccess()) {
             return result;
         }
 
@@ -196,6 +200,7 @@
 
         // FIXME: find those out, and possibly add them to the render params
         boolean hasNavigationBar = true;
+        //noinspection ConstantConditions
         IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
                 metrics, Surface.ROTATION_0,
                 hasNavigationBar);
@@ -229,10 +234,9 @@
             BridgeContext context = getContext();
             boolean isRtl = Bridge.isLocaleRtl(params.getLocale());
             int layoutDirection = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
-            ActionBarLayout actionBar = null;
 
             // the view group that receives the window background.
-            ViewGroup backgroundView = null;
+            ViewGroup backgroundView;
 
             if (mWindowIsFloating || params.isForceNoDecor()) {
                 backgroundView = mViewRoot = mContentRoot = new FrameLayout(context);
@@ -266,7 +270,7 @@
                         NavigationBar navigationBar = createNavigationBar(context,
                                 hardwareConfig.getDensity(), isRtl, params.isRtlSupported());
                         topLayout.addView(navigationBar);
-                    } catch (XmlPullParserException e) {
+                    } catch (XmlPullParserException ignored) {
 
                     }
                 }
@@ -322,7 +326,7 @@
                         StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(),
                                 layoutDirection, params.isRtlSupported());
                         topLayout.addView(statusBar);
-                    } catch (XmlPullParserException e) {
+                    } catch (XmlPullParserException ignored) {
 
                     }
                 }
@@ -339,20 +343,16 @@
 
                 // if the theme says no title/action bar, then the size will be 0
                 if (mActionBarSize > 0) {
-                    try {
-                        actionBar = createActionBar(context, params);
-                        backgroundLayout.addView(actionBar);
-                        actionBar.createMenuPopup();
-                        mContentRoot = actionBar.getContentRoot();
-                    } catch (XmlPullParserException e) {
-
-                    }
+                    ActionBarLayout actionBar = createActionBar(context, params);
+                    backgroundLayout.addView(actionBar);
+                    actionBar.createMenuPopup();
+                    mContentRoot = actionBar.getContentRoot();
                 } else if (mTitleBarSize > 0) {
                     try {
                         TitleBar titleBar = createTitleBar(context,
                                 hardwareConfig.getDensity(), params.getAppLabel());
                         backgroundLayout.addView(titleBar);
-                    } catch (XmlPullParserException e) {
+                    } catch (XmlPullParserException ignored) {
 
                     }
                 }
@@ -374,7 +374,7 @@
                         NavigationBar navigationBar = createNavigationBar(context,
                                 hardwareConfig.getDensity(), isRtl, params.isRtlSupported());
                         topLayout.addView(navigationBar);
-                    } catch (XmlPullParserException e) {
+                    } catch (XmlPullParserException ignored) {
 
                     }
                 }
@@ -399,7 +399,7 @@
             postInflateProcess(view, params.getProjectCallback());
 
             // get the background drawable
-            if (mWindowBackground != null && backgroundView != null) {
+            if (mWindowBackground != null) {
                 Drawable d = ResourceHelper.getDrawable(mWindowBackground, context);
                 backgroundView.setBackground(d);
             }
@@ -476,6 +476,7 @@
 
                     // first measure the full layout, with EXACTLY to get the size of the
                     // content as it is inside the decor/dialog
+                    @SuppressWarnings("deprecation")
                     Pair<Integer, Integer> exactMeasure = measureView(
                             mViewRoot, mContentRoot.getChildAt(0),
                             mMeasuredScreenWidth, MeasureSpec.EXACTLY,
@@ -483,6 +484,7 @@
 
                     // now measure the content only using UNSPECIFIED (where applicable, based on
                     // the rendering mode). This will give us the size the content needs.
+                    @SuppressWarnings("deprecation")
                     Pair<Integer, Integer> result = measureView(
                             mContentRoot, mContentRoot.getChildAt(0),
                             mMeasuredScreenWidth, widthMeasureSpecMode,
@@ -558,7 +560,7 @@
                     mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
                 }
 
-                if (freshRender && newImage == false) {
+                if (freshRender && !newImage) {
                     Graphics2D gc = mImage.createGraphics();
                     gc.setComposite(AlphaComposite.Src);
 
@@ -573,7 +575,8 @@
                 mViewRoot.draw(mCanvas);
             }
 
-            mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), false);
+            mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
+                    false);
 
             // success!
             return SUCCESS.createResult();
@@ -603,6 +606,7 @@
      * @param heightMode the MeasureSpec mode to use for the height.
      * @return the measured width/height if measuredView is non-null, null otherwise.
      */
+    @SuppressWarnings("deprecation")  // For the use of Pair
     private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
             int width, int widthMode, int height, int heightMode) {
         int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
@@ -633,7 +637,7 @@
         BridgeContext context = getContext();
 
         // find the animation file.
-        ResourceValue animationResource = null;
+        ResourceValue animationResource;
         int animationId = 0;
         if (isFrameworkAnimation) {
             animationResource = context.getRenderResources().getFrameworkResource(
@@ -723,7 +727,7 @@
 
         // add it to the parentView in the correct location
         Result result = addView(parentView, child, index);
-        if (result.isSuccess() == false) {
+        if (!result.isSuccess()) {
             return result;
         }
 
@@ -793,13 +797,13 @@
                     public void run() {
                         Result result = moveView(previousParent, newParentView, childView, index,
                                 params);
-                        if (result.isSuccess() == false) {
+                        if (!result.isSuccess()) {
                             listener.done(result);
                         }
 
                         // ready to do the work, acquire the scene.
                         result = acquire(250);
-                        if (result.isSuccess() == false) {
+                        if (!result.isSuccess()) {
                             listener.done(result);
                             return;
                         }
@@ -857,7 +861,7 @@
         }
 
         Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
-        if (result.isSuccess() == false) {
+        if (!result.isSuccess()) {
             return result;
         }
 
@@ -991,7 +995,7 @@
         }
 
         Result result = removeView(parent, childView);
-        if (result.isSuccess() == false) {
+        if (!result.isSuccess()) {
             return result;
         }
 
@@ -1019,7 +1023,7 @@
 
 
     private void findBackground(RenderResources resources) {
-        if (getParams().isBgColorOverridden() == false) {
+        if (!getParams().isBgColorOverridden()) {
             mWindowBackground = resources.findItemInTheme("windowBackground",
                     true /*isFrameworkAttr*/);
             if (mWindowBackground != null) {
@@ -1036,7 +1040,7 @@
         boolean windowFullscreen = getBooleanThemeValue(resources,
                 "windowFullscreen", false /*defaultValue*/);
 
-        if (windowFullscreen == false && mWindowIsFloating == false) {
+        if (!windowFullscreen && !mWindowIsFloating) {
             // default value
             mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT;
 
@@ -1090,7 +1094,7 @@
             boolean windowNoTitle = getBooleanThemeValue(resources,
                     "windowNoTitle", false /*defaultValue*/);
 
-            if (windowNoTitle == false) {
+            if (!windowNoTitle) {
 
                 // default size of the window title bar
                 mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT;
@@ -1117,7 +1121,7 @@
     }
 
     private void findNavigationBar(RenderResources resources, DisplayMetrics metrics) {
-        if (hasSoftwareButtons() && mWindowIsFloating == false) {
+        if (hasSoftwareButtons() && !mWindowIsFloating) {
 
             // default value
             mNavigationBarSize = 48; // ??
@@ -1131,15 +1135,12 @@
                 int shortSize = hardwareConfig.getScreenHeight();
 
                 // compute in dp
-                int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / hardwareConfig.getDensity().getDpiValue();
+                int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT /
+                        hardwareConfig.getDensity().getDpiValue();
 
-                if (shortSizeDp < 600) {
-                    // 0-599dp: "phone" UI with bar on the side
-                    barOnBottom = false;
-                } else {
-                    // 600+dp: "tablet" UI with bar on the bottom
-                    barOnBottom = true;
-                }
+                // 0-599dp: "phone" UI with bar on the side
+                // 600+dp: "tablet" UI with bar on the bottom
+                barOnBottom = shortSizeDp >= 600;
             }
 
             if (barOnBottom) {
@@ -1190,13 +1191,15 @@
     }
 
     /**
-     * Post process on a view hierachy that was just inflated.
-     * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the
+     * Post process on a view hierarchy that was just inflated.
+     * <p/>
+     * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
      * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
      * based on the content of the {@link FrameLayout}.
      * @param view the root view to process.
      * @param projectCallback callback to the project.
      */
+    @SuppressWarnings("deprecation")  // For the use of Pair
     private void postInflateProcess(View view, IProjectCallback projectCallback)
             throws PostInflateException {
         if (view instanceof TabHost) {
@@ -1299,7 +1302,7 @@
                     "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
         }
 
-        if ((v instanceof TabWidget) == false) {
+        if (!(v instanceof TabWidget)) {
             throw new PostInflateException(String.format(
                     "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
                     "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
@@ -1308,12 +1311,14 @@
         v = tabHost.findViewById(android.R.id.tabcontent);
 
         if (v == null) {
-            // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty)
+            // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
+            //noinspection SpellCheckingInspection
             throw new PostInflateException(
                     "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
         }
 
-        if ((v instanceof FrameLayout) == false) {
+        if (!(v instanceof FrameLayout)) {
+            //noinspection SpellCheckingInspection
             throw new PostInflateException(String.format(
                     "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
                     "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
@@ -1321,7 +1326,7 @@
 
         FrameLayout content = (FrameLayout)v;
 
-        // now process the content of the framelayout and dynamically create tabs for it.
+        // now process the content of the frameLayout and dynamically create tabs for it.
         final int count = content.getChildCount();
 
         // this must be called before addTab() so that the TabHost searches its TabWidget
@@ -1339,13 +1344,13 @@
                         }
                     });
             tabHost.addTab(spec);
-            return;
         } else {
-            // for each child of the framelayout, add a new TabSpec
+            // for each child of the frameLayout, add a new TabSpec
             for (int i = 0 ; i < count ; i++) {
                 View child = content.getChildAt(i);
                 String tabSpec = String.format("tab_spec%d", i+1);
                 int id = child.getId();
+                @SuppressWarnings("deprecation")
                 Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id);
                 String name;
                 if (resource != null) {
@@ -1468,13 +1473,13 @@
         ViewInfo result;
         if (isContentFrame) {
             result = new ViewInfo(view.getClass().getName(),
-                    getContext().getViewKey(view),
+                    getViewKey(view),
                     view.getLeft(), view.getTop() + offset, view.getRight(),
                     view.getBottom() + offset, view, view.getLayoutParams());
 
         } else {
             result = new SystemViewInfo(view.getClass().getName(),
-                    getContext().getViewKey(view),
+                    getViewKey(view),
                     view.getLeft(), view.getTop(), view.getRight(),
                     view.getBottom(), view, view.getLayoutParams());
         }
@@ -1495,6 +1500,32 @@
         return result;
     }
 
+    /**
+     * The cookie for menu items are stored in menu item and not in the map from View stored in
+     * BridgeContext.
+     */
+    private Object getViewKey(View view) {
+        BridgeContext context = getContext();
+        if (!(view instanceof MenuView.ItemView)) {
+            return context.getViewKey(view);
+        }
+        MenuItemImpl menuItem;
+        if (view instanceof ActionMenuItemView) {
+            menuItem = ((ActionMenuItemView) view).getItemData();
+        } else if (view instanceof ListMenuItemView) {
+            menuItem = ((ListMenuItemView) view).getItemData();
+        } else if (view instanceof IconMenuItemView) {
+            menuItem = ((IconMenuItemView) view).getItemData();
+        } else {
+            menuItem = null;
+        }
+        if (menuItem instanceof BridgeMenuItemImpl) {
+            return ((BridgeMenuItemImpl) menuItem).getViewCookie();
+        }
+
+        return null;
+    }
+
     private void invalidateRenderingSize() {
         mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
     }
@@ -1545,8 +1576,7 @@
     /**
      * Creates the action bar. Also queries the project callback for missing information.
      */
-    private ActionBarLayout createActionBar(BridgeContext context, SessionParams params)
-            throws XmlPullParserException {
+    private ActionBarLayout createActionBar(BridgeContext context, SessionParams params) {
         ActionBarLayout actionBar = new ActionBarLayout(context, params);
         actionBar.setLayoutParams(new LinearLayout.LayoutParams(
                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
index b16b4aa..19d249b 100644
--- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
+++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
@@ -253,4 +253,14 @@
 
         return true;
     }
+
+    @LayoutlibDelegate
+    /*package*/ static void setDefaultLocale(String locale) {
+        ICU.setDefaultLocale(locale);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static String getDefaultLocale() {
+        return ICU.getDefaultLocale();
+    }
 }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index e0c05de..986b911 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -143,6 +143,8 @@
         "android.view.ViewRootImpl#isInTouchMode",
         "android.view.WindowManagerGlobal#getWindowManagerService",
         "android.view.inputmethod.InputMethodManager#getInstance",
+        "android.view.MenuInflater#emptyMethod",
+        "com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
         "com.android.internal.util.XmlUtils#convertValueToInt",
         "com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
     };