Merge "Add flags to requests for package UID/GIDs."
diff --git a/api/current.txt b/api/current.txt
index 17b4ff3..83f8fdf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5762,6 +5762,7 @@
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
     method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String);
+    method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
     method public long getPasswordExpiration(android.content.ComponentName);
     method public long getPasswordExpirationTimeout(android.content.ComponentName);
     method public int getPasswordHistoryLength(android.content.ComponentName);
@@ -30781,7 +30782,7 @@
     field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type";
     field public static final java.lang.String COLUMN_SIZE = "_size";
     field public static final java.lang.String COLUMN_SUMMARY = "summary";
-    field public static final int FLAG_ARCHIVE = 2048; // 0x800
+    field public static final int FLAG_ARCHIVE = 1024; // 0x400
     field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
     field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
     field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -30790,9 +30791,8 @@
     field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100
     field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40
     field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
-    field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
     field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
-    field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400
+    field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200
     field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index c9cd437..87760b4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5890,6 +5890,7 @@
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
     method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String);
+    method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
     method public long getPasswordExpiration(android.content.ComponentName);
     method public long getPasswordExpirationTimeout(android.content.ComponentName);
     method public int getPasswordHistoryLength(android.content.ComponentName);
@@ -32814,7 +32815,7 @@
     field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type";
     field public static final java.lang.String COLUMN_SIZE = "_size";
     field public static final java.lang.String COLUMN_SUMMARY = "summary";
-    field public static final int FLAG_ARCHIVE = 2048; // 0x800
+    field public static final int FLAG_ARCHIVE = 1024; // 0x400
     field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
     field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
     field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -32823,9 +32824,8 @@
     field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100
     field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40
     field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
-    field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
     field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
-    field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400
+    field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200
     field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index c70cd34..c9584a3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5764,6 +5764,7 @@
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
     method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String);
+    method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
     method public long getPasswordExpiration(android.content.ComponentName);
     method public long getPasswordExpirationTimeout(android.content.ComponentName);
     method public int getPasswordHistoryLength(android.content.ComponentName);
@@ -30793,7 +30794,7 @@
     field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type";
     field public static final java.lang.String COLUMN_SIZE = "_size";
     field public static final java.lang.String COLUMN_SUMMARY = "summary";
-    field public static final int FLAG_ARCHIVE = 2048; // 0x800
+    field public static final int FLAG_ARCHIVE = 1024; // 0x400
     field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
     field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
     field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -30802,9 +30803,8 @@
     field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100
     field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40
     field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
-    field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
     field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
-    field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400
+    field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200
     field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 177fabe..4531a74 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -101,6 +101,9 @@
 import android.view.WindowManagerGlobal;
 import android.renderscript.RenderScriptCacheDir;
 import android.security.keystore.AndroidKeyStoreProvider;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.ErrnoException;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IVoiceInteractor;
@@ -820,48 +823,6 @@
 
             setCoreSettings(coreSettings);
 
-            /*
-             * Two possible indications that this package could be
-             * sharing its runtime with other packages:
-             *
-             * 1.) the sharedUserId attribute is set in the manifest,
-             *     indicating a request to share a VM with other
-             *     packages with the same sharedUserId.
-             *
-             * 2.) the application element of the manifest has an
-             *     attribute specifying a non-default process name,
-             *     indicating the desire to run in another packages VM.
-             *
-             * If sharing is enabled we do not have a unique application
-             * in a process and therefore cannot rely on the package
-             * name inside the runtime.
-             */
-            IPackageManager pm = getPackageManager();
-            android.content.pm.PackageInfo pi = null;
-            try {
-                pi = pm.getPackageInfo(appInfo.packageName, 0, UserHandle.myUserId());
-            } catch (RemoteException e) {
-            }
-            if (pi != null) {
-                boolean sharedUserIdSet = (pi.sharedUserId != null);
-                boolean processNameNotDefault =
-                (pi.applicationInfo != null &&
-                 !appInfo.packageName.equals(pi.applicationInfo.processName));
-                boolean sharable = (sharedUserIdSet || processNameNotDefault);
-
-                // Tell the VMRuntime about the application, unless it is shared
-                // inside a process.
-                if (!sharable) {
-                    final List<String> codePaths = new ArrayList<>();
-                    codePaths.add(appInfo.sourceDir);
-                    if (appInfo.splitSourceDirs != null) {
-                        Collections.addAll(codePaths, appInfo.splitSourceDirs);
-                    }
-                    VMRuntime.registerAppInfo(appInfo.packageName, appInfo.dataDir,
-                            codePaths.toArray(new String[codePaths.size()]));
-                }
-            }
-
             AppBindData data = new AppBindData();
             data.processName = processName;
             data.appInfo = appInfo;
@@ -4697,6 +4658,87 @@
         }
     }
 
+    private static void setupJitProfileSupport(LoadedApk loadedApk, File cacheDir) {
+        final ApplicationInfo appInfo = loadedApk.getApplicationInfo();
+        if (isSharingRuntime(appInfo)) {
+            // If sharing is enabled we do not have a unique application
+            // in a process and therefore cannot rely on the package
+            // name inside the runtime.
+            return;
+        }
+        final List<String> codePaths = new ArrayList<>();
+        if ((appInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+            codePaths.add(appInfo.sourceDir);
+        }
+        if (appInfo.splitSourceDirs != null) {
+            Collections.addAll(codePaths, appInfo.splitSourceDirs);
+        }
+
+        if (codePaths.isEmpty()) {
+            // If there are no code paths there's no need to setup a profile file and register with
+            // the runtime,
+            return;
+        }
+
+        // Add an extension to the file name to better reveal its intended use.
+        // Keep in sync with BackgroundDexOptService.
+        final String profileExtension = ".prof";
+        final File profileFile = new File(cacheDir, loadedApk.mPackageName + profileExtension);
+        if (!profileFile.exists()) {
+            FileDescriptor fd = null;
+            try {
+                final int permissions = 0600;  // read-write for user.
+                fd = Os.open(profileFile.getAbsolutePath(), OsConstants.O_CREAT, permissions);
+                Os.fchmod(fd, permissions);
+                Os.fchown(fd, appInfo.uid, appInfo.uid);
+            } catch (ErrnoException e) {
+                Log.w(TAG, "Unable to create jit profile file " + profileFile, e);
+                try {
+                    Os.unlink(profileFile.getAbsolutePath());
+                } catch (ErrnoException unlinkErr) {
+                    Log.v(TAG, "Unable to unlink jit profile file " + profileFile, unlinkErr);
+                }
+                return;
+            } finally {
+                IoUtils.closeQuietly(fd);
+            }
+        }
+
+        VMRuntime.registerAppInfo(profileFile.getAbsolutePath(), appInfo.dataDir,
+                codePaths.toArray(new String[codePaths.size()]));
+    }
+
+    /*
+     * Two possible indications that this package could be
+     * sharing its runtime with other packages:
+     *
+     * 1) the sharedUserId attribute is set in the manifest,
+     *    indicating a request to share a VM with other
+     *    packages with the same sharedUserId.
+     *
+     * 2) the application element of the manifest has an
+     *    attribute specifying a non-default process name,
+     *    indicating the desire to run in another packages VM.
+     */
+    private static boolean isSharingRuntime(ApplicationInfo appInfo) {
+        IPackageManager pm = getPackageManager();
+        android.content.pm.PackageInfo pi = null;
+        try {
+            pi = pm.getPackageInfo(appInfo.packageName, 0, UserHandle.myUserId());
+        } catch (RemoteException e) {
+        }
+        if (pi != null) {
+            boolean sharedUserIdSet = (pi.sharedUserId != null);
+            boolean processNameNotDefault = (pi.applicationInfo != null) &&
+                    !appInfo.packageName.equals(pi.applicationInfo.processName);
+            boolean sharable = sharedUserIdSet || processNameNotDefault;
+            return sharable;
+        }
+        // We couldn't get information for the package. Be pessimistic and assume
+        // it's sharing the runtime.
+        return true;
+    }
+
     private void updateDefaultDensity() {
         if (mCurDefaultDisplayDpi != Configuration.DENSITY_DPI_UNDEFINED
                 && mCurDefaultDisplayDpi != DisplayMetrics.DENSITY_DEVICE
@@ -4900,12 +4942,14 @@
                         + "due to missing cache directory");
             }
 
-            // Use codeCacheDir to store generated/compiled graphics code
+            // Use codeCacheDir to store generated/compiled graphics code and jit profiling data.
             final File codeCacheDir = appContext.getCodeCacheDir();
             if (codeCacheDir != null) {
                 setupGraphicsSupport(data.info, codeCacheDir);
+                setupJitProfileSupport(data.info, codeCacheDir);
             } else {
-                Log.e(TAG, "Unable to setupGraphicsSupport due to missing code-cache directory");
+                Log.e(TAG, "Unable to setupGraphicsSupport and setupJitProfileSupport " +
+                        "due to missing code-cache directory");
             }
         }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f940bd6..08e9b1e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -88,13 +88,15 @@
 
     private final Context mContext;
     private final IDevicePolicyManager mService;
+    private boolean mParentInstance;
 
     private static final String REMOTE_EXCEPTION_MESSAGE =
             "Failed to talk with device policy manager service";
 
-    private DevicePolicyManager(Context context) {
+    private DevicePolicyManager(Context context, boolean parentInstance) {
         this(context, IDevicePolicyManager.Stub.asInterface(
                         ServiceManager.getService(Context.DEVICE_POLICY_SERVICE)));
+        mParentInstance = parentInstance;
     }
 
     /** @hide */
@@ -106,7 +108,7 @@
 
     /** @hide */
     public static DevicePolicyManager create(Context context) {
-        DevicePolicyManager me = new DevicePolicyManager(context);
+        DevicePolicyManager me = new DevicePolicyManager(context, false);
         return me.mService != null ? me : null;
     }
 
@@ -1031,7 +1033,7 @@
     public void setPasswordQuality(@NonNull ComponentName admin, int quality) {
         if (mService != null) {
             try {
-                mService.setPasswordQuality(admin, quality);
+                mService.setPasswordQuality(admin, quality, mParentInstance);
             } catch (RemoteException e) {
                 Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
             }
@@ -1052,7 +1054,7 @@
     public int getPasswordQuality(@Nullable ComponentName admin, int userHandle) {
         if (mService != null) {
             try {
-                return mService.getPasswordQuality(admin, userHandle);
+                return mService.getPasswordQuality(admin, userHandle, mParentInstance);
             } catch (RemoteException e) {
                 Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
             }
@@ -1622,7 +1624,7 @@
     public boolean isActivePasswordSufficient() {
         if (mService != null) {
             try {
-                return mService.isActivePasswordSufficient(myUserId());
+                return mService.isActivePasswordSufficient(myUserId(), mParentInstance);
             } catch (RemoteException e) {
                 Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
             }
@@ -1791,6 +1793,9 @@
      * not acceptable for the current constraints or if the user has not been decrypted yet.
      */
     public boolean resetPassword(String password, int flags) {
+        if (mParentInstance) {
+            throw new SecurityException("Reset password does not work across profiles.");
+        }
         if (mService != null) {
             try {
                 return mService.resetPassword(password, flags);
@@ -3060,6 +3065,8 @@
      *
      * <p>If the device owner information is {@code null} or empty then the device owner info is
      * cleared and the user owner info is shown on the lock screen if it is set.
+     * <p>If the device owner information contains only whitespaces then the message on the lock
+     * screen will be blank and the user will not be allowed to change it.
      *
      * @param admin The name of the admin component to check.
      * @param info Device owner information which will be displayed instead of the user
@@ -4927,4 +4934,23 @@
         }
         return null;
     }
+
+    /**
+     * Obtains a {@link DevicePolicyManager} whose calls act on the parent profile.
+     *
+     * <p> Note only some methods will work on the parent Manager.
+     *
+     * @return a new instance of {@link DevicePolicyManager} that acts on the parent profile.
+     */
+    public DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
+        try {
+            if (!mService.isManagedProfile(admin)) {
+                throw new SecurityException("The current user does not have a parent profile.");
+            }
+            return new DevicePolicyManager(mContext, true);
+        } catch (RemoteException re) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index f480a02..754cb43 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -35,8 +35,8 @@
  * {@hide}
  */
 interface IDevicePolicyManager {
-    void setPasswordQuality(in ComponentName who, int quality);
-    int getPasswordQuality(in ComponentName who, int userHandle);
+    void setPasswordQuality(in ComponentName who, int quality, boolean parent);
+    int getPasswordQuality(in ComponentName who, int userHandle, boolean parent);
 
     void setPasswordMinimumLength(in ComponentName who, int length);
     int getPasswordMinimumLength(in ComponentName who, int userHandle);
@@ -67,7 +67,7 @@
 
     long getPasswordExpiration(in ComponentName who, int userHandle);
 
-    boolean isActivePasswordSufficient(int userHandle);
+    boolean isActivePasswordSufficient(int userHandle, boolean parent);
     int getCurrentFailedPasswordAttempts(int userHandle);
     int getProfileWithMinimumFailedPasswordsForWipe(int userHandle);
 
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index c3625b8..469a4ea 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -98,5 +98,11 @@
     void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId);
     void setClient(IPrintSpoolerClient client);
     void setPrintJobCancelling(in PrintJobId printJobId, boolean cancelling);
-    void removeApprovedPrintService(in ComponentName serviceToRemove);
+
+    /**
+     * Remove all approved print services that are not in the given set.
+     *
+     * @param servicesToKeep The names of the services to keep
+     */
+    void pruneApprovedPrintServices(in List<ComponentName> servicesToKeep);
 }
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index b33ef83..91e01f2 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -16,6 +16,7 @@
 
 package android.printservice;
 
+import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -94,6 +95,16 @@
     }
 
     /**
+     * Return the component name for this print service.
+     *
+     * @return The component name for this print service.
+     */
+    public @NonNull ComponentName getComponentName() {
+        return new ComponentName(mResolveInfo.serviceInfo.packageName,
+                mResolveInfo.serviceInfo.name);
+    }
+
+    /**
      * Creates a new instance.
      *
      * @param resolveInfo The service resolve info.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 084ff77..a401ac2 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -230,7 +230,6 @@
          * @see #FLAG_SUPPORTS_WRITE
          * @see #FLAG_SUPPORTS_DELETE
          * @see #FLAG_SUPPORTS_THUMBNAIL
-         * @see #FLAG_SUPPORTS_TYPED_DOCUMENT
          * @see #FLAG_DIR_PREFERS_GRID
          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
          * @see #FLAG_VIRTUAL_DOCUMENT
@@ -349,15 +348,6 @@
         public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
 
         /**
-         * Flag indicating that a document can be converted to alternative types.
-         *
-         * @see #COLUMN_FLAGS
-         * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
-         *      android.os.CancellationSignal)
-         */
-        public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 1 << 9;
-
-        /**
          * Flag indicating that a document is virtual, and doesn't have byte
          * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
          *
@@ -366,7 +356,7 @@
          * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
          *      android.os.CancellationSignal)
          */
-        public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 10;
+        public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
 
         /**
          * Flag indicating that a document is an archive, and it's contents can be
@@ -378,7 +368,7 @@
          * @see #COLUMN_FLAGS
          * @see DocumentsProvider#queryChildDocuments(String, String[], String)
          */
-        public static final int FLAG_ARCHIVE = 1 << 11;
+        public static final int FLAG_ARCHIVE = 1 << 10;
 
         /**
          * Flag indicating that document titles should be hidden when viewing
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index e25ba35..94b4157 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -517,13 +517,12 @@
      *            provider.
      * @param signal used by the caller to signal if the request should be
      *            cancelled. May be null.
-     * @see Document#FLAG_SUPPORTS_TYPED_DOCUMENT
      */
     @SuppressWarnings("unused")
     public AssetFileDescriptor openTypedDocument(
             String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
             throws FileNotFoundException {
-        throw new UnsupportedOperationException("Typed documents not supported");
+        throw new FileNotFoundException("The requested MIME type is not supported.");
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d0f2159..df7e6da 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4974,19 +4974,22 @@
 
         /**
          * List of the enabled print services.
+         *
+         * N and beyond uses {@link #DISABLED_PRINT_SERVICES}. But this might be used in an upgrade
+         * from pre-N.
+         *
          * @hide
          */
         public static final String ENABLED_PRINT_SERVICES =
             "enabled_print_services";
 
         /**
-         * List of the system print services we enabled on first boot. On
-         * first boot we enable all system, i.e. bundled print services,
-         * once, so they work out-of-the-box.
+         * List of the disabled print services.
+         *
          * @hide
          */
-        public static final String ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES =
-            "enabled_on_first_boot_system_print_services";
+        public static final String DISABLED_PRINT_SERVICES =
+            "disabled_print_services";
 
         /**
          * Setting to always use the default text-to-speech settings regardless
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index d9068dc..44811cb 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -150,6 +150,7 @@
                     ds[0].mX = event.getX();
                     ds[0].mY = event.getY();
 
+                    int nx = widget.getScrollX() + (int) dx;
                     int ny = widget.getScrollY() + (int) dy;
 
                     int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
@@ -161,6 +162,8 @@
                     int oldX = widget.getScrollX();
                     int oldY = widget.getScrollY();
 
+                    scrollTo(widget, layout, nx, ny);
+
                     // If we actually scrolled, then cancel the up action.
                     if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
                         widget.cancelLongPress();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3a1e9ab..68f1ac3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3592,6 +3592,9 @@
 
     private int[] mDrawableState = null;
 
+    /** Whether draw() is currently being called. */
+    private boolean mInDraw = false;
+
     ViewOutlineProvider mOutlineProvider = ViewOutlineProvider.BACKGROUND;
 
     /**
@@ -16470,6 +16473,8 @@
      */
     @CallSuper
     public void draw(Canvas canvas) {
+        mInDraw = true;
+
         final int privateFlags = mPrivateFlags;
         final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
@@ -16514,6 +16519,7 @@
             onDrawForeground(canvas);
 
             // we're done...
+            mInDraw = false;
             return;
         }
 
@@ -16661,6 +16667,8 @@
 
         // Step 6, draw decorations (foreground, scrollbars)
         onDrawForeground(canvas);
+
+        mInDraw = false;
     }
 
     /**
@@ -17105,7 +17113,8 @@
      */
     @Override
     public void invalidateDrawable(@NonNull Drawable drawable) {
-        if (verifyDrawable(drawable)) {
+        // Don't invalidate if a drawable changes during drawing.
+        if (verifyDrawable(drawable) && !mInDraw) {
             final Rect dirty = drawable.getDirtyBounds();
             final int scrollX = mScrollX;
             final int scrollY = mScrollY;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1c9f3b4..0fb3951 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -384,6 +384,8 @@
         int localChanges;
     }
 
+    private String mTag = TAG;
+
     public ViewRootImpl(Context context, Display display) {
         mContext = context;
         mWindowSession = WindowManagerGlobal.getWindowSession();
@@ -510,6 +512,7 @@
                     mWindowAttributes.packageName = mBasePackageName;
                 }
                 attrs = mWindowAttributes;
+                setTag();
                 // Keep track of the actual window flags supplied by the client.
                 mClientWindowLayoutFlags = attrs.flags;
 
@@ -546,7 +549,7 @@
                     attrs.backup();
                     mTranslator.translateWindowLayout(attrs);
                 }
-                if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs);
+                if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);
 
                 if (!compatibilityInfo.supportsScreen()) {
                     attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
@@ -607,7 +610,7 @@
                 mPendingContentInsets.set(mAttachInfo.mContentInsets);
                 mPendingStableInsets.set(mAttachInfo.mStableInsets);
                 mPendingVisibleInsets.set(0, 0, 0, 0);
-                if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow);
+                if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
                 if (res < WindowManagerGlobal.ADD_OKAY) {
                     mAttachInfo.mRootView = null;
                     mAdded = false;
@@ -701,6 +704,13 @@
         }
     }
 
+    private void setTag() {
+        final String[] split = mWindowAttributes.getTitle().toString().split("\\.");
+        if (split.length > 0) {
+            mTag = TAG + "[" + split[split.length - 1] + "]";
+        }
+    }
+
     /** Whether the window is in local focus mode or not */
     private boolean isInLocalFocusMode() {
         return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
@@ -990,7 +1000,7 @@
     @Override
     public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
         checkThread();
-        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
+        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
 
         if (dirty == null) {
             invalidate();
@@ -1174,7 +1184,7 @@
 
     private boolean collectViewAttributes() {
         if (mAttachInfo.mRecomputeGlobalAttributes) {
-            //Log.i(TAG, "Computing view hierarchy attributes!");
+            //Log.i(mTag, "Computing view hierarchy attributes!");
             mAttachInfo.mRecomputeGlobalAttributes = false;
             boolean oldScreenOn = mAttachInfo.mKeepScreenOn;
             mAttachInfo.mKeepScreenOn = false;
@@ -1215,7 +1225,7 @@
         int childHeightMeasureSpec;
         boolean windowSizeMayChange = false;
 
-        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG,
+        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
                 "Measuring " + host + " in display " + desiredWindowWidth
                 + "x" + desiredWindowHeight + "...");
 
@@ -1231,26 +1241,26 @@
             if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                 baseSize = (int)mTmpValue.getDimension(packageMetrics);
             }
-            if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize);
+            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize);
             if (baseSize != 0 && desiredWindowWidth > baseSize) {
                 childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-                if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
+                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                         + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                 if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                     goodMeasure = true;
                 } else {
                     // Didn't fit in that size... try expanding a bit.
                     baseSize = (baseSize+desiredWindowWidth)/2;
-                    if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize="
+                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                             + baseSize);
                     childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-                    if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
+                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                             + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                     if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
-                        if (DEBUG_DIALOG) Log.v(TAG, "Good!");
+                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                         goodMeasure = true;
                     }
                 }
@@ -1350,8 +1360,8 @@
         int desiredWindowHeight;
 
         final int viewVisibility = getHostVisibility();
-        boolean viewVisibilityChanged = mViewVisibility != viewVisibility
-                || mNewSurfaceNeeded;
+        final boolean viewVisibilityChanged = !mFirst
+                && (mViewVisibility != viewVisibility || mNewSurfaceNeeded);
 
         WindowManager.LayoutParams params = null;
         if (mWindowAttributesChanged) {
@@ -1401,7 +1411,6 @@
             mAttachInfo.mHasWindowFocus = false;
             mAttachInfo.mWindowVisibility = viewVisibility;
             mAttachInfo.mRecomputeGlobalAttributes = false;
-            viewVisibilityChanged = false;
             mLastConfiguration.setTo(host.getResources().getConfiguration());
             mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
             // Set the layout direction if it has not been set before (inherit is the default)
@@ -1411,14 +1420,13 @@
             host.dispatchAttachedToWindow(mAttachInfo, 0);
             mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
             dispatchApplyInsets(host);
-            //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);
+            //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
 
         } else {
             desiredWindowWidth = frame.width();
             desiredWindowHeight = frame.height();
             if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
-                if (DEBUG_ORIENTATION) Log.v(TAG,
-                        "View " + host + " resized to: " + frame);
+                if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
                 mFullRedrawNeeded = true;
                 mLayoutRequested = true;
                 windowSizeMayChange = true;
@@ -1471,28 +1479,22 @@
                 }
                 if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
                     mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
-                    if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
+                    if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
                 if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
                     insetsChanged = true;
                 }
-                if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
-                        || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                if ((lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
+                        || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT)
+                        && (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
+                                || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD)) {
                     windowSizeMayChange = true;
-
-                    if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
-                            || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
-                        // NOTE -- system code, won't try to do compat mode.
-                        Point size = new Point();
-                        mDisplay.getRealSize(size);
-                        desiredWindowWidth = size.x;
-                        desiredWindowHeight = size.y;
-                    } else {
-                        DisplayMetrics packageMetrics = res.getDisplayMetrics();
-                        desiredWindowWidth = packageMetrics.widthPixels;
-                        desiredWindowHeight = packageMetrics.heightPixels;
-                    }
+                    // NOTE -- system code, won't try to do compat mode.
+                    Point size = new Point();
+                    mDisplay.getRealSize(size);
+                    desiredWindowWidth = size.x;
+                    desiredWindowHeight = size.y;
                 }
             }
 
@@ -1616,7 +1618,7 @@
 
             try {
                 if (DEBUG_LAYOUT) {
-                    Log.i(TAG, "host=w:" + host.getMeasuredWidth() + ", h:" +
+                    Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" +
                             host.getMeasuredHeight() + ", params=" + params);
                 }
 
@@ -1634,7 +1636,7 @@
                 final int surfaceGenerationId = mSurface.getGenerationId();
                 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
 
-                if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString()
+                if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
                         + " overscan=" + mPendingOverscanInsets.toShortString()
                         + " content=" + mPendingContentInsets.toShortString()
                         + " visible=" + mPendingVisibleInsets.toShortString()
@@ -1643,7 +1645,7 @@
                         + " surface=" + mSurface);
 
                 if (mPendingConfiguration.seq != 0) {
-                    if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: "
+                    if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
                             + mPendingConfiguration);
                     updateConfiguration(new Configuration(mPendingConfiguration), !mFirst);
                     mPendingConfiguration.seq = 0;
@@ -1662,19 +1664,19 @@
                         & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
                 if (contentInsetsChanged) {
                     mAttachInfo.mContentInsets.set(mPendingContentInsets);
-                    if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
+                    if (DEBUG_LAYOUT) Log.v(mTag, "Content insets changing to: "
                             + mAttachInfo.mContentInsets);
                 }
                 if (overscanInsetsChanged) {
                     mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
-                    if (DEBUG_LAYOUT) Log.v(TAG, "Overscan insets changing to: "
+                    if (DEBUG_LAYOUT) Log.v(mTag, "Overscan insets changing to: "
                             + mAttachInfo.mOverscanInsets);
                     // Need to relayout with content insets.
                     contentInsetsChanged = true;
                 }
                 if (stableInsetsChanged) {
                     mAttachInfo.mStableInsets.set(mPendingStableInsets);
-                    if (DEBUG_LAYOUT) Log.v(TAG, "Decor insets changing to: "
+                    if (DEBUG_LAYOUT) Log.v(mTag, "Decor insets changing to: "
                             + mAttachInfo.mStableInsets);
                     // Need to relayout with content insets.
                     contentInsetsChanged = true;
@@ -1691,7 +1693,7 @@
                 }
                 if (visibleInsetsChanged) {
                     mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
-                    if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
+                    if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
 
@@ -1872,7 +1874,7 @@
                     int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                     int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
 
-                    if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed!  mWidth="
+                    if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
                             + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                             + " mHeight=" + mHeight
                             + " measuredHeight=" + host.getMeasuredHeight()
@@ -1902,7 +1904,7 @@
                     }
 
                     if (measureAgain) {
-                        if (DEBUG_LAYOUT) Log.v(TAG,
+                        if (DEBUG_LAYOUT) Log.v(mTag,
                                 "And hey let's measure once more: width=" + width
                                 + " height=" + height);
                         performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
@@ -2024,15 +2026,15 @@
 
         if (mFirst) {
             // handle first focus request
-            if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: mView.hasFocus()="
+            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
                     + mView.hasFocus());
             if (mView != null) {
                 if (!mView.hasFocus()) {
                     mView.requestFocus(View.FOCUS_FORWARD);
-                    if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view="
+                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
                             + mView.findFocus());
                 } else {
-                    if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view="
+                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
                             + mView.findFocus());
                 }
             }
@@ -2104,11 +2106,11 @@
     }
 
     private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
-        Log.e(TAG, "OutOfResourcesException initializing HW surface", e);
+        Log.e(mTag, "OutOfResourcesException initializing HW surface", e);
         try {
             if (!mWindowSession.outOfMemory(mWindow) &&
                     Process.myUid() != Process.SYSTEM_UID) {
-                Slog.w(TAG, "No processes killed for memory; killing self");
+                Slog.w(mTag, "No processes killed for memory; killing self");
                 Process.killProcess(Process.myPid());
             }
         } catch (RemoteException ex) {
@@ -2184,7 +2186,7 @@
 
         final View host = mView;
         if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
-            Log.v(TAG, "Laying out " + host + " to (" +
+            Log.v(mTag, "Laying out " + host + " to (" +
                     host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
         }
 
@@ -2427,11 +2429,11 @@
             String thisHash = Integer.toHexString(System.identityHashCode(this));
             long frameTime = nowTime - mFpsPrevTime;
             long totalTime = nowTime - mFpsStartTime;
-            Log.v(TAG, "0x" + thisHash + "\tFrame time:\t" + frameTime);
+            Log.v(mTag, "0x" + thisHash + "\tFrame time:\t" + frameTime);
             mFpsPrevTime = nowTime;
             if (totalTime > 1000) {
                 float fps = (float) mFpsNumFrames * 1000 / totalTime;
-                Log.v(TAG, "0x" + thisHash + "\tFPS:\t" + fps);
+                Log.v(mTag, "0x" + thisHash + "\tFPS:\t" + fps);
                 mFpsStartTime = nowTime;
                 mFpsNumFrames = 0;
             }
@@ -2473,7 +2475,7 @@
                 try {
                     mWindowDrawCountDown.await();
                 } catch (InterruptedException e) {
-                    Log.e(TAG, "Window redraw count down interruped!");
+                    Log.e(mTag, "Window redraw count down interruped!");
                 }
                 mWindowDrawCountDown = null;
             }
@@ -2483,7 +2485,7 @@
             }
 
             if (LOCAL_LOGV) {
-                Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
+                Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
             }
             if (mSurfaceHolder != null && mSurface.isValid()) {
                 mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
@@ -2566,7 +2568,7 @@
         }
 
         if (DEBUG_ORIENTATION || DEBUG_DRAW) {
-            Log.v(TAG, "Draw " + mView + "/"
+            Log.v(mTag, "Draw " + mView + "/"
                     + mWindowAttributes.getTitle()
                     + ": dirty={" + dirty.left + "," + dirty.top
                     + "," + dirty.right + "," + dirty.bottom + "} surface="
@@ -2700,7 +2702,7 @@
             handleOutOfResourcesException(e);
             return false;
         } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Could not lock surface", e);
+            Log.e(mTag, "Could not lock surface", e);
             // Don't assume this is due to out of memory, it could be
             // something else, and if it is something else then we could
             // kill stuff (or ourself) for no reason.
@@ -2710,7 +2712,7 @@
 
         try {
             if (DEBUG_ORIENTATION || DEBUG_DRAW) {
-                Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
+                Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                         + canvas.getWidth() + ", h=" + canvas.getHeight());
                 //canvas.drawARGB(255, 255, 0, 0);
             }
@@ -2733,7 +2735,7 @@
 
             if (DEBUG_DRAW) {
                 Context cxt = mView.getContext();
-                Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
+                Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                         ", metrics=" + cxt.getResources().getDisplayMetrics() +
                         ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
             }
@@ -2758,14 +2760,14 @@
             try {
                 surface.unlockCanvasAndPost(canvas);
             } catch (IllegalArgumentException e) {
-                Log.e(TAG, "Could not unlock surface", e);
+                Log.e(mTag, "Could not unlock surface", e);
                 mLayoutRequested = true;    // ask wm for a new surface next time.
                 //noinspection ReturnInsideFinallyBlock
                 return false;
             }
 
             if (LOCAL_LOGV) {
-                Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
+                Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
             }
         }
         return true;
@@ -2870,14 +2872,14 @@
                 // view is visible.
                 rectangle = null;
             }
-            if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Eval scroll: focus=" + focus
+            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Eval scroll: focus=" + focus
                     + " rectangle=" + rectangle + " ci=" + ci
                     + " vi=" + vi);
             if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
                 // Optimization: if the focus hasn't changed since last
                 // time, and no layout has happened, then just leave things
                 // as they are.
-                if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Keeping scroll y="
+                if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Keeping scroll y="
                         + mScrollY + " vi=" + vi.toShortString());
             } else {
                 // We need to determine if the currently focused view is
@@ -2885,51 +2887,51 @@
                 // a pan so it can be seen.
                 mLastScrolledFocus = new WeakReference<View>(focus);
                 mScrollMayChange = false;
-                if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Need to scroll?");
+                if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Need to scroll?");
                 // Try to find the rectangle from the focus view.
                 if (focus.getGlobalVisibleRect(mVisRect, null)) {
-                    if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Root w="
+                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Root w="
                             + mView.getWidth() + " h=" + mView.getHeight()
                             + " ci=" + ci.toShortString()
                             + " vi=" + vi.toShortString());
                     if (rectangle == null) {
                         focus.getFocusedRect(mTempRect);
-                        if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Focus " + focus
+                        if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Focus " + focus
                                 + ": focusRect=" + mTempRect.toShortString());
                         if (mView instanceof ViewGroup) {
                             ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                     focus, mTempRect);
                         }
-                        if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                 "Focus in window: focusRect="
                                 + mTempRect.toShortString()
                                 + " visRect=" + mVisRect.toShortString());
                     } else {
                         mTempRect.set(rectangle);
-                        if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                 "Request scroll to rect: "
                                 + mTempRect.toShortString()
                                 + " visRect=" + mVisRect.toShortString());
                     }
                     if (mTempRect.intersect(mVisRect)) {
-                        if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                        if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                 "Focus window visible rect: "
                                 + mTempRect.toShortString());
                         if (mTempRect.height() >
                                 (mView.getHeight()-vi.top-vi.bottom)) {
                             // If the focus simply is not going to fit, then
                             // best is probably just to leave things as-is.
-                            if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                     "Too tall; leaving scrollY=" + scrollY);
                         } else if ((mTempRect.top-scrollY) < vi.top) {
                             scrollY -= vi.top - (mTempRect.top-scrollY);
-                            if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                     "Top covered; scrollY=" + scrollY);
                         } else if ((mTempRect.bottom-scrollY)
                                 > (mView.getHeight()-vi.bottom)) {
                             scrollY += (mTempRect.bottom-scrollY)
                                     - (mView.getHeight()-vi.bottom);
-                            if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+                            if (DEBUG_INPUT_RESIZE) Log.v(mTag,
                                     "Bottom covered; scrollY=" + scrollY);
                         }
                         handled = true;
@@ -2939,7 +2941,7 @@
         }
 
         if (scrollY != mScrollY) {
-            if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old="
+            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Pan scroll changed: old="
                     + mScrollY + " , new=" + scrollY);
             if (!immediate) {
                 if (mScroller == null) {
@@ -3018,7 +3020,7 @@
 
     void setPointerCapture(View view) {
         if (!mAttachInfo.mHasWindowFocus) {
-            Log.w(TAG, "Can't set capture if it's not focused.");
+            Log.w(mTag, "Can't set capture if it's not focused.");
             return;
         }
         if (mCapturingView == view) {
@@ -3044,7 +3046,7 @@
     @Override
     public void requestChildFocus(View child, View focused) {
         if (DEBUG_INPUT_RESIZE) {
-            Log.v(TAG, "Request child focus: focus now " + focused);
+            Log.v(mTag, "Request child focus: focus now " + focused);
         }
         checkThread();
         scheduleTraversals();
@@ -3053,7 +3055,7 @@
     @Override
     public void clearChildFocus(View child) {
         if (DEBUG_INPUT_RESIZE) {
-            Log.v(TAG, "Clearing child focus");
+            Log.v(mTag, "Clearing child focus");
         }
         checkThread();
         scheduleTraversals();
@@ -3152,7 +3154,7 @@
     }
 
     void updateConfiguration(Configuration config, boolean force) {
-        if (DEBUG_CONFIGURATION) Log.v(TAG,
+        if (DEBUG_CONFIGURATION) Log.v(mTag,
                 "Applying new config to window "
                 + mWindowAttributes.getTitle()
                 + ": " + config);
@@ -3388,10 +3390,10 @@
                                 mAttachInfo.mHardwareRenderer.initializeIfNeeded(
                                         mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
                             } catch (OutOfResourcesException e) {
-                                Log.e(TAG, "OutOfResourcesException locking surface", e);
+                                Log.e(mTag, "OutOfResourcesException locking surface", e);
                                 try {
                                     if (!mWindowSession.outOfMemory(mWindow)) {
-                                        Slog.w(TAG, "No processes killed for memory; killing self");
+                                        Slog.w(mTag, "No processes killed for memory; killing self");
                                         Process.killProcess(Process.myPid());
                                     }
                                 } catch (RemoteException ex) {
@@ -3714,7 +3716,7 @@
          */
         protected void onDeliverToNext(QueuedInputEvent q) {
             if (DEBUG_INPUT_STAGES) {
-                Log.v(TAG, "Done with " + getClass().getSimpleName() + ". " + q);
+                Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
             }
             if (mNext != null) {
                 mNext.deliver(q);
@@ -3725,7 +3727,7 @@
 
         protected boolean shouldDropInputEvent(QueuedInputEvent q) {
             if (mView == null || !mAdded) {
-                Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent);
+                Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
                 return true;
             } else if ((!mAttachInfo.mHasWindowFocus
                     && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) || mStopped
@@ -3736,12 +3738,12 @@
                 if (isTerminalInputEvent(q.mEvent)) {
                     // Don't drop terminal input events, however mark them as canceled.
                     q.mEvent.cancel();
-                    Slog.w(TAG, "Cancelling event due to no window focus: " + q.mEvent);
+                    Slog.w(mTag, "Cancelling event due to no window focus: " + q.mEvent);
                     return false;
                 }
 
                 // Drop non-terminal input events.
-                Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent);
+                Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent);
                 return true;
             }
             return false;
@@ -3981,7 +3983,7 @@
                 InputMethodManager imm = InputMethodManager.peekInstance();
                 if (imm != null) {
                     final InputEvent event = q.mEvent;
-                    if (DEBUG_IMF) Log.v(TAG, "Sending input event to IME: " + event);
+                    if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event);
                     int result = imm.dispatchInputEvent(event, q, this, mHandler);
                     if (result == InputMethodManager.DISPATCH_HANDLED) {
                         return FINISH_HANDLED;
@@ -4413,7 +4415,7 @@
                     break;
             }
 
-            if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + mX.position + " step="
+            if (DEBUG_TRACKBALL) Log.v(mTag, "TB X=" + mX.position + " step="
                     + mX.step + " dir=" + mX.dir + " acc=" + mX.acceleration
                     + " move=" + event.getX()
                     + " / Y=" + mY.position + " step="
@@ -4452,11 +4454,11 @@
             if (keycode != 0) {
                 if (movement < 0) movement = -movement;
                 int accelMovement = (int)(movement * accel);
-                if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement
+                if (DEBUG_TRACKBALL) Log.v(mTag, "Move: movement=" + movement
                         + " accelMovement=" + accelMovement
                         + " accel=" + accel);
                 if (accelMovement > movement) {
-                    if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: "
+                    if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
                             + keycode);
                     movement--;
                     int repeatCount = accelMovement - movement;
@@ -4466,7 +4468,7 @@
                             InputDevice.SOURCE_KEYBOARD));
                 }
                 while (movement > 0) {
-                    if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: "
+                    if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: "
                             + keycode);
                     movement--;
                     curTime = SystemClock.uptimeMillis();
@@ -4708,7 +4710,7 @@
                 update(event, true);
                 break;
             default:
-                Log.w(TAG, "Unexpected action: " + event.getActionMasked());
+                Log.w(mTag, "Unexpected action: " + event.getActionMasked());
             }
         }
 
@@ -5347,7 +5349,7 @@
                             mWindowSession.dragRecipientEntered(mWindow);
                         }
                     } catch (RemoteException e) {
-                        Slog.e(TAG, "Unable to note drag target change");
+                        Slog.e(mTag, "Unable to note drag target change");
                     }
                 }
 
@@ -5355,10 +5357,10 @@
                 if (what == DragEvent.ACTION_DROP) {
                     mDragDescription = null;
                     try {
-                        Log.i(TAG, "Reporting drop result: " + result);
+                        Log.i(mTag, "Reporting drop result: " + result);
                         mWindowSession.reportDropResult(mWindow, result);
                     } catch (RemoteException e) {
-                        Log.e(TAG, "Unable to report drop result");
+                        Log.e(mTag, "Unable to report drop result");
                     }
                 }
 
@@ -5444,14 +5446,14 @@
             mTranslator.translateWindowLayout(params);
         }
         if (params != null) {
-            if (DBG) Log.d(TAG, "WindowLayout in layoutWindow:" + params);
+            if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
         }
         mPendingConfiguration.seq = 0;
-        //Log.d(TAG, ">>>>>> CALLING relayout");
+        //Log.d(mTag, ">>>>>> CALLING relayout");
         if (params != null && mOrigWindowType != params.type) {
             // For compatibility with old apps, don't crash here.
             if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-                Slog.w(TAG, "Window type can not be changed after "
+                Slog.w(mTag, "Window type can not be changed after "
                         + "the window is added; ignoring change of " + mView);
                 params.type = mOrigWindowType;
             }
@@ -5463,7 +5465,7 @@
                 viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                 mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                 mPendingStableInsets, mPendingOutsets, mPendingConfiguration, mSurface);
-        //Log.d(TAG, "<<<<<< BACK FROM relayout");
+        //Log.d(mTag, "<<<<<< BACK FROM relayout");
         if (restore) {
             params.restore();
         }
@@ -5510,7 +5512,7 @@
             }
         } catch (IllegalStateException e) {
             // Exception thrown by getAudioManager() when mView is null
-            Log.e(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e);
+            Log.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);
             e.printStackTrace();
         }
     }
@@ -5633,7 +5635,7 @@
         if (!mIsDrawing) {
             destroyHardwareRenderer();
         } else {
-            Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
+            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                     "  window=" + this + ", title=" + mWindowAttributes.getTitle());
         }
         mHandler.sendEmptyMessage(MSG_DIE);
@@ -5642,7 +5644,7 @@
 
     void doDie() {
         checkThread();
-        if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
+        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
         synchronized (this) {
             if (mRemoved) {
                 return;
@@ -5735,7 +5737,7 @@
     public void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
             Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
             Configuration newConfig, Rect backDropFrame) {
-        if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": frame=" + frame.toShortString()
+        if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString()
                 + " contentInsets=" + contentInsets.toShortString()
                 + " visibleInsets=" + visibleInsets.toShortString()
                 + " reportDraw=" + reportDraw
@@ -5773,7 +5775,7 @@
     }
 
     public void dispatchMoved(int newX, int newY) {
-        if (DEBUG_LAYOUT) Log.v(TAG, "Window moved " + this + ": newX=" + newX + " newY=" + newY);
+        if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY);
         if (mTranslator != null) {
             PointF point = new PointF(newX, newY);
             mTranslator.translatePointInScreenToAppWindow(point);
@@ -6690,7 +6692,7 @@
     }
 
     void changeCanvasOpacity(boolean opaque) {
-        Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque);
+        Log.d(mTag, "changeCanvasOpacity: opaque=" + opaque);
         if (mAttachInfo.mHardwareRenderer != null) {
             mAttachInfo.mHardwareRenderer.setOpaque(opaque);
         }
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 0032f17..48d1d2b 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -20,6 +20,8 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -134,7 +136,7 @@
     }
 
     @Override
-    public void initForMenu(Context context, MenuBuilder menu) {
+    public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
         super.initForMenu(context, menu);
 
         final Resources res = context.getResources();
@@ -629,8 +631,16 @@
     }
 
     public boolean flagActionItems() {
-        final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
-        final int itemsSize = visibleItems.size();
+        final ArrayList<MenuItemImpl> visibleItems;
+        final int itemsSize;
+        if (mMenu != null) {
+            visibleItems = mMenu.getVisibleItems();
+            itemsSize = visibleItems.size();
+        } else {
+            visibleItems = null;
+            itemsSize = 0;
+        }
+
         int maxActions = mMaxItems;
         int widthLimit = mActionItemWidthLimit;
         final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
@@ -786,7 +796,7 @@
         if (isVisible) {
             // Not a submenu, but treat it like one.
             super.onSubMenuSelected(null);
-        } else {
+        } else if (mMenu != null) {
             mMenu.close(false /* closeAllMenus */);
         }
     }
@@ -938,7 +948,9 @@
 
         @Override
         protected void onDismiss() {
-            mMenu.close();
+            if (mMenu != null) {
+                mMenu.close();
+            }
             mOverflowPopup = null;
 
             super.onDismiss();
@@ -999,7 +1011,9 @@
         }
 
         public void run() {
-            mMenu.changeMenuMode();
+            if (mMenu != null) {
+                mMenu.changeMenuMode();
+            }
             final View menuView = (View) mMenuView;
             if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
                 mOverflowPopup = mPopup;
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 1f02c3b..4d0a1c8 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -622,7 +622,7 @@
     }
 
     /** @hide */
-    public void initialize(MenuBuilder menu) {
+    public void initialize(@Nullable MenuBuilder menu) {
         mMenu = menu;
     }
 
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index acbf5eb..8e711b0 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -2070,7 +2070,7 @@
         MenuItemImpl mCurrentExpandedItem;
 
         @Override
-        public void initForMenu(Context context, MenuBuilder menu) {
+        public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
             // Clear the expanded action view when menus change.
             if (mMenu != null && mCurrentExpandedItem != null) {
                 mMenu.collapseItemActionView(mCurrentExpandedItem);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 40eaaf7..cc2f714 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -23,7 +23,6 @@
 import com.android.internal.view.StandaloneActionMode;
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.android.internal.view.menu.MenuHelper;
-import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.widget.ActionBarContextView;
 import com.android.internal.widget.BackgroundFallback;
 import com.android.internal.widget.DecorCaptionView;
@@ -72,6 +71,7 @@
 import static android.app.ActivityManager.StackId;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.View.MeasureSpec.AT_MOST;
 import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.getMode;
@@ -194,7 +194,12 @@
     private Drawable mCaptionBackgroundDrawable;
     private Drawable mUserCaptionBackgroundDrawable;
 
-    DecorView(Context context, int featureId, PhoneWindow window) {
+    private float mAvailableWidth;
+
+    String mLogTag = TAG;
+
+    DecorView(Context context, int featureId, PhoneWindow window,
+            WindowManager.LayoutParams params) {
         super(context);
         mFeatureId = featureId;
 
@@ -210,7 +215,11 @@
         mSemiTransparentStatusBarColor = context.getResources().getColor(
                 R.color.system_bar_background_semi_transparent, null /* theme */);
 
+        updateAvailableWidth();
+
         setWindow(window);
+
+        updateLogTag(params);
     }
 
     void setBackgroundFallback(int resId) {
@@ -408,7 +417,7 @@
 
         if (mFeatureId >= 0) {
             if (action == MotionEvent.ACTION_DOWN) {
-                Log.i(TAG, "Watchiing!");
+                Log.i(mLogTag, "Watchiing!");
                 mWatchingForMenu = true;
                 mDownY = (int) event.getY();
                 return false;
@@ -421,7 +430,7 @@
             int y = (int)event.getY();
             if (action == MotionEvent.ACTION_MOVE) {
                 if (y > (mDownY+30)) {
-                    Log.i(TAG, "Closing!");
+                    Log.i(mLogTag, "Closing!");
                     mWindow.closePanel(mFeatureId);
                     mWatchingForMenu = false;
                     return true;
@@ -433,13 +442,13 @@
             return false;
         }
 
-        //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
+        //Log.i(mLogTag, "Intercept: action=" + action + " y=" + event.getY()
         //        + " (in " + getHeight() + ")");
 
         if (action == MotionEvent.ACTION_DOWN) {
             int y = (int)event.getY();
             if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
-                Log.i(TAG, "Watching!");
+                Log.i(mLogTag, "Watching!");
                 mWatchingForMenu = true;
             }
             return false;
@@ -452,7 +461,7 @@
         int y = (int)event.getY();
         if (action == MotionEvent.ACTION_MOVE) {
             if (y < (getHeight()-30)) {
-                Log.i(TAG, "Opening!");
+                Log.i(mLogTag, "Opening!");
                 mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent(
                         KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
                 mWatchingForMenu = false;
@@ -543,15 +552,15 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
-        final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+        final boolean isPortrait =
+                getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
 
         final int widthMode = getMode(widthMeasureSpec);
         final int heightMode = getMode(heightMeasureSpec);
 
         boolean fixedWidth = false;
         if (widthMode == AT_MOST) {
-            final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
-                    : mWindow.mFixedWidthMajor;
+            final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor;
             if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
                 final int w;
                 if (tvw.type == TypedValue.TYPE_DIMENSION) {
@@ -623,7 +632,7 @@
                 if (tv.type == TypedValue.TYPE_DIMENSION) {
                     min = (int)tv.getDimension(metrics);
                 } else if (tv.type == TypedValue.TYPE_FRACTION) {
-                    min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
+                    min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth);
                 } else {
                     min = 0;
                 }
@@ -1217,7 +1226,7 @@
                     int fop = fg.getOpacity();
                     int bop = bg.getOpacity();
                     if (false)
-                        Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
+                        Log.v(mLogTag, "Background opacity: " + bop + ", Frame opacity: " + fop);
                     if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
                         opacity = PixelFormat.OPAQUE;
                     } else if (fop == PixelFormat.UNKNOWN) {
@@ -1232,16 +1241,16 @@
                     // frame with padding... there is no way to tell if the
                     // frame and background together will draw all pixels.
                     if (false)
-                        Log.v(TAG, "Padding: " + mFramePadding);
+                        Log.v(mLogTag, "Padding: " + mFramePadding);
                     opacity = PixelFormat.TRANSLUCENT;
                 }
             }
             if (false)
-                Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
+                Log.v(mLogTag, "Background: " + bg + ", Frame: " + fg);
         }
 
         if (false)
-            Log.v(TAG, "Selected default opacity: " + opacity);
+            Log.v(mLogTag, "Selected default opacity: " + opacity);
 
         mDefaultOpacity = opacity;
         if (mFeatureId < 0) {
@@ -1600,6 +1609,7 @@
                 enableCaption(StackId.hasWindowDecor(workspaceId));
             }
         }
+        updateAvailableWidth();
         initializeElevation();
     }
 
@@ -1744,7 +1754,7 @@
 
         // We shouldn't really get here as the background fallback should be always available since
         // it is defaulted by the system.
-        Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + mWindow);
+        Log.w(mLogTag, "Failed to find background drawable for PhoneWindow=" + mWindow);
         return null;
     }
 
@@ -1761,7 +1771,7 @@
             try {
                 workspaceId = callback.getWindowStackId();
             } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
+                Log.e(mLogTag, "Failed to get the workspace ID of a PhoneWindow.");
             }
         }
         if (workspaceId == INVALID_STACK_ID) {
@@ -1927,6 +1937,19 @@
         }
     }
 
+    void updateLogTag(WindowManager.LayoutParams params) {
+        final String[] split = params.getTitle().toString().split("\\.");
+        if (split.length > 0) {
+            mLogTag = TAG + "[" + split[split.length - 1] + "]";
+        }
+    }
+
+    private void updateAvailableWidth() {
+        Resources res = getResources();
+        mAvailableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                res.getConfiguration().screenWidthDp, res.getDisplayMetrics());
+    }
+
     private static class ColorViewState {
         View view = null;
         int targetVisibility = View.INVISIBLE;
@@ -1988,12 +2011,12 @@
                 isPrimary = mode == mPrimaryActionMode;
                 isFloating = mode == mFloatingActionMode;
                 if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) {
-                    Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
+                    Log.e(mLogTag, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
                             + mode + " was not the current primary action mode! Expected "
                             + mPrimaryActionMode);
                 }
                 if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) {
-                    Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
+                    Log.e(mLogTag, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
                             + mode + " was not the current floating action mode! Expected "
                             + mFloatingActionMode);
                 }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 2f951cc..f159a4d 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -113,6 +113,8 @@
 
     private final static String TAG = "PhoneWindow";
 
+    private static final boolean DEBUG = false;
+
     private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
 
     private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
@@ -2286,7 +2288,7 @@
         } else {
             context = getContext();
         }
-        return new DecorView(context, featureId, this);
+        return new DecorView(context, featureId, this, getAttributes());
     }
 
     protected ViewGroup generateLayout(DecorView decor) {
@@ -2365,6 +2367,8 @@
 
         a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
         a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
+        if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
+                + ", major: " + mMinWidthMajor.coerceToString());
         if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
             if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
             a.getValue(R.styleable.Window_windowFixedWidthMajor,
@@ -3781,4 +3785,12 @@
     int getDecorCaptionShade() {
         return mDecorCaptionShade;
     }
+
+    @Override
+    public void setAttributes(WindowManager.LayoutParams params) {
+        super.setAttributes(params);
+        if (mDecor != null) {
+            mDecor.updateLogTag(params);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 849d314..4dd71e7 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -46,7 +46,7 @@
     void cancelPreloadRecentApps();
     void showScreenPinningRequest();
 
-    void showKeyboardShortcutsMenu();
+    void toggleKeyboardShortcutsMenu();
 
     /**
      * Notifies the status bar that an app transition is pending to delay applying some flags with
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 0a4ad06..0125d37 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -68,7 +68,7 @@
     void preloadRecentApps();
     void cancelPreloadRecentApps();
 
-    void showKeyboardShortcutsMenu();
+    void toggleKeyboardShortcutsMenu();
 
     /**
      * Notifies the status bar that an app transition is pending to delay applying some flags with
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 406b487..dc66818 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -779,7 +779,7 @@
         @Override
         public final void handleMessage(Message msg) {
             if (!mHasQuit) {
-                if (mSm != null) {
+                if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
                     mSm.onPreHandleMessage(msg);
                 }
 
@@ -807,7 +807,7 @@
                 // We need to check if mSm == null here as we could be quitting.
                 if (mDbg && mSm != null) mSm.log("handleMessage: X");
 
-                if (mSm != null) {
+                if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
                     mSm.onPostHandleMessage(msg);
                 }
             }
diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
index 92e9ea6..7ac0ac3 100644
--- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.view.menu;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -58,7 +60,7 @@
     }
 
     @Override
-    public void initForMenu(Context context, MenuBuilder menu) {
+    public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
         mContext = context;
         mInflater = LayoutInflater.from(mContext);
         mMenu = menu;
diff --git a/core/java/com/android/internal/view/menu/IconMenuPresenter.java b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
index 2439b5d..5223a7b 100644
--- a/core/java/com/android/internal/view/menu/IconMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
@@ -17,6 +17,8 @@
 
 import com.android.internal.view.menu.MenuView.ItemView;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -49,7 +51,7 @@
     }
 
     @Override
-    public void initForMenu(Context context, MenuBuilder menu) {
+    public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
         super.initForMenu(context, menu);
         mMaxItems = -1;
     }
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
index c476354..2fff3ba 100644
--- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.view.menu;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -76,7 +78,7 @@
     }
 
     @Override
-    public void initForMenu(Context context, MenuBuilder menu) {
+    public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
         if (mThemeRes != 0) {
             mContext = new ContextThemeWrapper(context, mThemeRes);
             mInflater = LayoutInflater.from(mContext);
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 465d775..31b2f96 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -17,6 +17,7 @@
 package com.android.internal.view.menu;
 
 
+import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -1027,23 +1028,24 @@
         mIsActionItemsStale = true;
         onItemsChanged(true);
     }
-    
+
+    @NonNull
     public ArrayList<MenuItemImpl> getVisibleItems() {
         if (!mIsVisibleItemsStale) return mVisibleItems;
-        
+
         // Refresh the visible items
         mVisibleItems.clear();
-        
+
         final int itemsSize = mItems.size(); 
         MenuItemImpl item;
         for (int i = 0; i < itemsSize; i++) {
             item = mItems.get(i);
             if (item.isVisible()) mVisibleItems.add(item);
         }
-        
+
         mIsVisibleItemsStale = false;
         mIsActionItemsStale = true;
-        
+
         return mVisibleItems;
     }
 
diff --git a/core/java/com/android/internal/view/menu/MenuPopup.java b/core/java/com/android/internal/view/menu/MenuPopup.java
index 98f5d90..b151f34 100644
--- a/core/java/com/android/internal/view/menu/MenuPopup.java
+++ b/core/java/com/android/internal/view/menu/MenuPopup.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.view.menu;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.view.MenuItem;
 import android.view.View;
@@ -73,7 +75,7 @@
     public abstract void setOnDismissListener(PopupWindow.OnDismissListener listener);
 
     @Override
-    public void initForMenu(Context context, MenuBuilder menu) {
+    public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
         // Don't need to do anything; we added as a presenter in the constructor.
     }
 
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
index c847c15..65bdc09 100644
--- a/core/java/com/android/internal/view/menu/MenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.view.menu;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Parcelable;
 import android.view.ViewGroup;
@@ -49,14 +51,16 @@
     }
 
     /**
-     * Initialize this presenter for the given context and menu.
-     * This method is called by MenuBuilder when a presenter is
-     * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
+     * Initializes this presenter for the given context and menu.
+     * <p>
+     * This method is called by MenuBuilder when a presenter is added. See
+     * {@link MenuBuilder#addMenuPresenter(MenuPresenter)}.
      *
-     * @param context Context for this presenter; used for view creation and resource management
-     * @param menu Menu to host
+     * @param context the context for this presenter; used for view creation
+     *                and resource management, must be non-{@code null}
+     * @param menu the menu to host, or {@code null} to clear the hosted menu
      */
-    public void initForMenu(Context context, MenuBuilder menu);
+    public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu);
 
     /**
      * Retrieve a MenuView to display the menu specified in
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 825e336..f90b59d 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -17,6 +17,8 @@
 package com.android.internal.widget;
 
 import android.animation.LayoutTransition;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActionBar;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -1593,7 +1595,7 @@
         MenuItemImpl mCurrentExpandedItem;
 
         @Override
-        public void initForMenu(Context context, MenuBuilder menu) {
+        public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
             // Clear the expanded action view when menus change.
             if (mMenu != null && mCurrentExpandedItem != null) {
                 mMenu.collapseItemActionView(mCurrentExpandedItem);
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index d8ec22a..92f7812 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -260,6 +260,15 @@
         return nullObjectReturn("SkAndroidCodec::NewFromStream returned null");
     }
 
+    // Do not allow ninepatch decodes to 565.  In the past, decodes to 565
+    // would dither, and we do not want to pre-dither ninepatches, since we
+    // know that they will be stretched.  We no longer dither 565 decodes,
+    // but we continue to prevent ninepatches from decoding to 565, in order
+    // to maintain the old behavior.
+    if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) {
+        prefColorType = kN32_SkColorType;
+    }
+
     // Determine the output size and return if the client only wants the size.
     SkISize size = codec->getSampledDimensions(sampleSize);
     if (options != NULL) {
@@ -369,15 +378,7 @@
         case SkCodec::kIncompleteInput:
             break;
         default:
-            return nullObjectReturn("codec->getAndoridPixels() failed.");
-    }
-
-    // Some images may initially report that they have alpha due to the format
-    // of the encoded data, but then never use any colors which have alpha
-    // less than 100%.  Here we check if the image really had alpha, and
-    // mark it as opaque if it is actually opaque.
-    if (kOpaque_SkAlphaType != alphaType && !codec->reallyHasAlpha()) {
-        decodingBitmap.setAlphaType(kOpaque_SkAlphaType);
+            return nullObjectReturn("codec->getAndroidPixels() failed.");
     }
 
     int scaledWidth = size.width();
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 7860b74..768c7d4 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1049,6 +1049,10 @@
     pJniStorage->mDeviceCallback.clear();
 }
 
+static jint android_media_AudioTrack_get_FCC_8(JNIEnv *env, jobject thiz) {
+    return FCC_8;
+}
+
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
@@ -1106,6 +1110,7 @@
     {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId},
     {"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback},
     {"native_disableDeviceCallback", "()V", (void *)android_media_AudioTrack_disableDeviceCallback},
+    {"native_get_FCC_8",     "()I",      (void *)android_media_AudioTrack_get_FCC_8},
 };
 
 
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index bb4f7d9..a810ff1 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -97,9 +97,10 @@
     /** Maximum value for sample rate */
     private static final int SAMPLE_RATE_HZ_MAX = 192000;
 
-    // FCC_8
-    /** Maximum value for AudioTrack channel count */
-    private static final int CHANNEL_COUNT_MAX = 8;
+    /** Maximum value for AudioTrack channel count
+     * @hide public for MediaCode only, do not un-hide or change to a numeric literal
+     */
+    public static final int CHANNEL_COUNT_MAX = native_get_FCC_8();
 
     /** indicates AudioTrack state is stopped */
     public static final int PLAYSTATE_STOPPED = 1;  // matches SL_PLAYSTATE_STOPPED
@@ -2583,6 +2584,7 @@
     private native final int native_getRoutedDeviceId();
     private native final void native_enableDeviceCallback();
     private native final void native_disableDeviceCallback();
+    static private native int native_get_FCC_8();
 
     //---------------------------------------------------------
     // Utility methods
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index e6bc8f1..9bcb5e3 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -907,7 +907,7 @@
             } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
                 sampleRateRange = Range.create(1, 96000);
                 bitRates = Range.create(1, 10000000);
-                maxChannels = 8;
+                maxChannels = AudioTrack.CHANNEL_COUNT_MAX;
             } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
                 sampleRateRange = Range.create(1, 655350);
                 // lossless codec, so bitrate is ignored
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 70d651f..b63df6f 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -655,7 +655,7 @@
        goto error;
     }
 
-    if ((numChannels < 1) || (numChannels > 8)) {
+    if ((numChannels < 1) || (numChannels > FCC_8)) {
         ALOGE("Sample channel count (%d) out of range", numChannels);
         status = BAD_VALUE;
         goto error;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 6a5911b..34614b4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -502,11 +502,6 @@
         // If the file is virtual, but can be converted to another format, then try to copy it
         // as such format. Also, append an extension for the target mime type (if known).
         if (srcInfo.isVirtualDocument()) {
-            if (!srcInfo.isTypedDocument()) {
-                // Impossible to copy a file which is virtual, but not typed.
-                mFailedFiles.add(srcInfo);
-                return false;
-            }
             final String[] streamTypes = getContentResolver().getStreamTypes(
                     srcInfo.derivedUri, "*/*");
             if (streamTypes != null && streamTypes.length > 0) {
@@ -516,8 +511,7 @@
                 dstDisplayName = srcInfo.displayName +
                         (extension != null ? "." + extension : srcInfo.displayName);
             } else {
-                // The provider says that it supports typed documents, but doesn't say
-                // anything about available formats.
+                // The virtual file is not available as any alternative streamable format.
                 // TODO: Log failures. b/26192412
                 mFailedFiles.add(srcInfo);
                 return false;
@@ -640,9 +634,8 @@
 
         boolean success = true;
         try {
-            // If the file is virtual, but can be converted to another format, then try to copy it
-            // as such format.
-            if (srcInfo.isVirtualDocument() && srcInfo.isTypedDocument()) {
+            // If the file is virtual, then try to copy it as an alternative format.
+            if (srcInfo.isVirtualDocument()) {
                 final AssetFileDescriptor srcFileAsAsset =
                         mSrcClient.openTypedAssetFileDescriptor(
                                 srcInfo.derivedUri, mimeType, null, canceller);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 215c6e6..83df18c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -255,10 +255,6 @@
         return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
     }
 
-    public boolean isTypedDocument() {
-        return (flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) != 0;
-    }
-
     public int hashCode() {
         return derivedUri.hashCode() + mimeType.hashCode();
     }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
index 24a8113..4ce0185 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
@@ -311,10 +311,8 @@
 
     public void testCopyVirtualNonTypedFile() throws Exception {
         String srcPath = "/non-typed.sth";
-        // Empty stream types causes the FLAG_SUPPORTS_TYPED_DOCUMENT to be not set.
-        ArrayList<String> streamTypes = new ArrayList<>();
         Uri testFile = mStorage.createVirtualFile(SRC_ROOT, srcPath, "virtual/mime-type",
-                streamTypes, "I love Tokyo!".getBytes());
+                null /* streamTypes */, "I love Tokyo!".getBytes());
 
         Intent intent = createCopyIntent(Lists.newArrayList(testFile));
         startService(intent);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 2c311a7..50f4628 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -316,12 +316,9 @@
             String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
             throws FileNotFoundException {
         final StubDocument document = mStorage.get(documentId);
-        if (document == null || !document.file.isFile()) {
+        if (document == null || !document.file.isFile() || document.streamTypes == null) {
             throw new FileNotFoundException();
         }
-        if ((document.flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) == 0) {
-            throw new IllegalStateException("Tried to open a non-typed document as typed.");
-        }
         for (final String mimeType : document.streamTypes) {
             // Strict compare won't accept wildcards, but that's OK for tests, as DocumentsUI
             // doesn't use them for getStreamTypes nor openTypedDocument.
@@ -349,13 +346,13 @@
             throw new IllegalArgumentException(
                     "The provided Uri is incorrect, or the file is gone.");
         }
-        if ((document.flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) == 0) {
-            return null;
-        }
         if (!"*/*".equals(mimeTypeFilter)) {
             // Not used by DocumentsUI, so don't bother implementing it.
             throw new UnsupportedOperationException();
         }
+        if (document.streamTypes == null) {
+            return null;
+        }
         return document.streamTypes.toArray(new String[document.streamTypes.size()]);
     }
 
@@ -628,9 +625,6 @@
                 File file, String mimeType, List<String> streamTypes, StubDocument parent) {
             int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE
                     | Document.FLAG_VIRTUAL_DOCUMENT;
-            if (streamTypes.size() > 0) {
-                flags |= Document.FLAG_SUPPORTS_TYPED_DOCUMENT;
-            }
             return new StubDocument(file, mimeType, streamTypes, flags, parent);
         }
 
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 97a7bff..b662c58 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -178,12 +178,6 @@
     <!-- Template for the notification label for a blocked print job. [CHAR LIMIT=25] -->
     <string name="blocked_notification_title_template">Printer blocked <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
-    <!-- Template for the notification label for a composite (multiple items) print jobs notification. [CHAR LIMIT=25] -->
-    <plurals name="composite_notification_title_template">
-        <item quantity="one"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print job</item>
-        <item quantity="other"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print jobs</item>
-    </plurals>
-
     <!-- Label for the notification button for cancelling a print job. [CHAR LIMIT=25] -->
     <string name="cancel">Cancel</string>
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 3dc5d7e..0210693 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -40,6 +40,7 @@
 import android.print.PrintJobInfo;
 import android.print.PrintManager;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.printspooler.R;
@@ -61,13 +62,22 @@
 
     private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
 
+    private static final String PRINT_JOB_NOTIFICATION_GROUP_KEY = "PRINT_JOB_NOTIFICATIONS";
+    private static final String PRINT_JOB_NOTIFICATION_SUMMARY = "PRINT_JOB_NOTIFICATIONS_SUMMARY";
+
     private final Context mContext;
     private final NotificationManager mNotificationManager;
 
+    /**
+     * Mapping from printJobIds to their notification Ids.
+     */
+    private final ArraySet<PrintJobId> mNotifications;
+
     public NotificationController(Context context) {
         mContext = context;
         mNotificationManager = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mNotifications = new ArraySet<>(0);
     }
 
     public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
@@ -81,16 +91,44 @@
             }
         }
 
-        updateNotification(notifyPrintJobs);
+        updateNotifications(notifyPrintJobs);
     }
 
-    private void updateNotification(List<PrintJobInfo> printJobs) {
-        if (printJobs.size() <= 0) {
-            removeNotification();
-        } else if (printJobs.size() == 1) {
-            createSimpleNotification(printJobs.get(0));
-        } else {
+    /**
+     * Update notifications for the given print jobs, remove all other notifications.
+     *
+     * @param printJobs The print job that we want to create notifications for.
+     */
+    private void updateNotifications(List<PrintJobInfo> printJobs) {
+        ArraySet<PrintJobId> removedPrintJobs = new ArraySet<>(mNotifications);
+
+        final int numPrintJobs = printJobs.size();
+
+        // Create summary notification
+        if (numPrintJobs > 1) {
             createStackedNotification(printJobs);
+        } else {
+            mNotificationManager.cancel(PRINT_JOB_NOTIFICATION_SUMMARY, 0);
+        }
+
+        // Create per print job notification
+        for (int i = 0; i < numPrintJobs; i++) {
+            PrintJobInfo printJob = printJobs.get(i);
+            PrintJobId printJobId = printJob.getId();
+
+            removedPrintJobs.remove(printJobId);
+            mNotifications.add(printJobId);
+
+            createSimpleNotification(printJob);
+        }
+
+        // Remove notifications for print jobs that do not exist anymore
+        final int numRemovedPrintJobs = removedPrintJobs.size();
+        for (int i = 0; i < numRemovedPrintJobs; i++) {
+            PrintJobId removedPrintJob = removedPrintJobs.valueAt(i);
+
+            mNotificationManager.cancel(removedPrintJob.flattenToString(), 0);
+            mNotifications.remove(removedPrintJob);
         }
     }
 
@@ -148,7 +186,8 @@
                 .setOngoing(true)
                 .setShowWhen(true)
                 .setColor(mContext.getColor(
-                        com.android.internal.R.color.system_notification_accent_color));
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY);
 
         if (firstAction != null) {
             builder.addAction(firstAction);
@@ -176,7 +215,7 @@
             builder.setContentText(printJob.getPrinterName());
         }
 
-        mNotificationManager.notify(0, builder.build());
+        mNotificationManager.notify(printJob.getId().flattenToString(), 0, builder.build());
     }
 
     private void createPrintingNotification(PrintJobInfo printJob) {
@@ -204,33 +243,36 @@
                 .setContentIntent(createContentIntent(null))
                 .setWhen(System.currentTimeMillis())
                 .setOngoing(true)
-                .setShowWhen(true);
+                .setShowWhen(true)
+                .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY)
+                .setGroupSummary(true);
 
         final int printJobCount = printJobs.size();
 
         InboxStyle inboxStyle = new InboxStyle();
-        inboxStyle.setBigContentTitle(String.format(mContext.getResources().getQuantityText(
-                R.plurals.composite_notification_title_template,
-                printJobCount).toString(), printJobCount));
 
+        int icon = com.android.internal.R.drawable.ic_print;
         for (int i = printJobCount - 1; i>= 0; i--) {
             PrintJobInfo printJob = printJobs.get(i);
-            if (i == printJobCount - 1) {
-                builder.setLargeIcon(((BitmapDrawable) mContext.getResources().getDrawable(
-                        computeNotificationIcon(printJob), null)).getBitmap());
-                builder.setSmallIcon(computeNotificationIcon(printJob));
-                builder.setContentTitle(computeNotificationTitle(printJob));
-                builder.setContentText(printJob.getPrinterName());
-            }
+
             inboxStyle.addLine(computeNotificationTitle(printJob));
+
+            // if any print job is in an error state show an error icon for the summary
+            if (printJob.getState() == PrintJobInfo.STATE_FAILED
+                    || printJob.getState() == PrintJobInfo.STATE_BLOCKED) {
+                icon = com.android.internal.R.drawable.ic_print_error;
+            }
         }
 
+        builder.setSmallIcon(icon);
+        builder.setLargeIcon(
+                ((BitmapDrawable) mContext.getResources().getDrawable(icon, null)).getBitmap());
         builder.setNumber(printJobCount);
         builder.setStyle(inboxStyle);
         builder.setColor(mContext.getColor(
                 com.android.internal.R.color.system_notification_accent_color));
 
-        mNotificationManager.notify(0, builder.build());
+        mNotificationManager.notify(PRINT_JOB_NOTIFICATION_SUMMARY, 0, builder.build());
     }
 
     private String computeNotificationTitle(PrintJobInfo printJob) {
@@ -264,10 +306,6 @@
         }
     }
 
-    private void removeNotification() {
-        mNotificationManager.cancel(0);
-    }
-
     private PendingIntent createContentIntent(PrintJobId printJobId) {
         Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
         if (printJobId != null) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index 496a0b0..18160ff 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -1416,9 +1416,9 @@
         }
 
         @Override
-        public void removeApprovedPrintService(ComponentName serviceToRemove) {
+        public void pruneApprovedPrintServices(List<ComponentName> servicesToKeep) {
             (new ApprovedPrintServices(PrintSpoolerService.this))
-                    .removeApprovedService(serviceToRemove);
+                    .pruneApprovedServices(servicesToKeep);
         }
 
         @Override
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index cdfc7ee..81727ab 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -66,6 +66,7 @@
 import android.widget.SearchView;
 import android.widget.TextView;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.printspooler.R;
 
 import java.util.ArrayList;
@@ -101,7 +102,8 @@
     private AnnounceFilterResult mAnnounceFilterResult;
 
     /** Monitor if new print services get enabled or disabled */
-    private ContentObserver mPrintServicesObserver;
+    private ContentObserver mPrintServicesDisabledObserver;
+    private PackageMonitor mPackageObserver;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -245,28 +247,45 @@
      * Register listener for changes to the enabled print services.
      */
     private void registerServiceMonitor() {
-        mPrintServicesObserver = new ContentObserver(new Handler()) {
+        // Listen for services getting disabled
+        mPrintServicesDisabledObserver = new ContentObserver(new Handler()) {
             @Override
             public void onChange(boolean selfChange) {
                 onPrintServicesUpdate();
             }
         };
 
+        // Listen for services getting installed or uninstalled
+        mPackageObserver = new PackageMonitor() {
+            @Override
+            public void onPackageModified(String packageName) {
+                onPrintServicesUpdate();
+            }
+
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                onPrintServicesUpdate();
+            }
+
+            @Override
+            public void onPackageAdded(String packageName, int uid) {
+                onPrintServicesUpdate();
+            }
+        };
+
         getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.ENABLED_PRINT_SERVICES), false,
-                mPrintServicesObserver);
+                Settings.Secure.getUriFor(Settings.Secure.DISABLED_PRINT_SERVICES), false,
+                mPrintServicesDisabledObserver);
+
+        mPackageObserver.register(this, getMainLooper(), false);
     }
 
     /**
-     * Unregister {@link #mPrintServicesObserver listener for changes to the enabled print services}
-     * or nothing if the listener is not registered.
+     * Unregister the listeners for changes to the enabled print services.
      */
     private void unregisterServiceMonitorIfNeeded() {
-        if (mPrintServicesObserver != null) {
-            getContentResolver().unregisterContentObserver(mPrintServicesObserver);
-
-            mPrintServicesObserver = null;
-        }
+        getContentResolver().unregisterContentObserver(mPrintServicesDisabledObserver);
+        mPackageObserver.unregister();
     }
 
     @Override
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java b/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java
index dd10567..a1e3ef4 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java
@@ -23,6 +23,7 @@
 import android.printservice.PrintService;
 import android.util.ArraySet;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -126,29 +127,27 @@
     }
 
     /**
-     * If a {@link PrintService} is approved, remove it from the list of approved services.
+     * Remove all approved {@link PrintService print services} that are not in the given set.
      *
-     * @param serviceToRemove The {@link ComponentName} of the {@link PrintService} to be removed
+     * @param serviceNamesToKeep The {@link ComponentName names } of the services to keep
      */
-    public void removeApprovedService(ComponentName serviceToRemove) {
+    public void pruneApprovedServices(List<ComponentName> serviceNamesToKeep) {
         synchronized (sLock) {
-            if (isApprovedService(serviceToRemove)) {
-                // Copy approved services.
-                ArraySet<String> approvedServices = new ArraySet<String>(
-                        mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null));
+            Set<String> approvedServices = getApprovedServices();
+            Set<String> newApprovedServices = new ArraySet<>(approvedServices.size());
 
+            final int numServiceNamesToKeep = serviceNamesToKeep.size();
+            for(int i = 0; i < numServiceNamesToKeep; i++) {
+                String serviceToKeep = serviceNamesToKeep.get(i).flattenToShortString();
+                if (approvedServices.contains(serviceToKeep)) {
+                    newApprovedServices.add(serviceToKeep);
+                }
+            }
+
+            if (approvedServices.size() != newApprovedServices.size()) {
                 SharedPreferences.Editor editor = mPreferences.edit();
 
-                final int numApprovedServices = approvedServices.size();
-                for (int i = 0; i < numApprovedServices; i++) {
-                    if (approvedServices.valueAt(i)
-                            .equals(serviceToRemove.flattenToShortString())) {
-                        approvedServices.removeAt(i);
-                        break;
-                    }
-                }
-
-                editor.putStringSet(APPROVED_SERVICES_PREFERENCE, approvedServices);
+                editor.putStringSet(APPROVED_SERVICES_PREFERENCE, newApprovedServices);
                 editor.apply();
             }
         }
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index f7a2d75..00a6cbd 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -21,6 +21,7 @@
 import static com.android.shell.BugreportPrefs.getWarningState;
 
 import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -28,10 +29,13 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
 import java.text.NumberFormat;
 import java.util.ArrayList;
+import java.util.Enumeration;
 import java.util.List;
 import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
 import libcore.io.Streams;
@@ -813,6 +817,9 @@
             Log.e(TAG, "INTERNAL ERROR: no info for PID " + pid + ": " + mProcesses);
             return;
         }
+
+        addDetailsToZipFile(info);
+
         final Intent sendIntent = buildSendIntent(mContext, info);
         final Intent notifIntent;
 
@@ -868,7 +875,7 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                info.bugreportFile = zipBugreport(info.bugreportFile);
+                zipBugreport(info);
                 sendBugreportNotification(context, info);
                 return null;
             }
@@ -879,35 +886,92 @@
      * Zips a bugreport file, returning the path to the new file (or to the
      * original in case of failure).
      */
-    private static File zipBugreport(File bugreportFile) {
-        String bugreportPath = bugreportFile.getAbsolutePath();
-        String zippedPath = bugreportPath.replace(".txt", ".zip");
+    private static void zipBugreport(BugreportInfo info) {
+        final String bugreportPath = info.bugreportFile.getAbsolutePath();
+        final String zippedPath = bugreportPath.replace(".txt", ".zip");
         Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
-        File bugreportZippedFile = new File(zippedPath);
-        try (InputStream is = new FileInputStream(bugreportFile);
+        final File bugreportZippedFile = new File(zippedPath);
+        try (InputStream is = new FileInputStream(info.bugreportFile);
                 ZipOutputStream zos = new ZipOutputStream(
                         new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
-            ZipEntry entry = new ZipEntry(bugreportFile.getName());
-            entry.setTime(bugreportFile.lastModified());
-            zos.putNextEntry(entry);
-            int totalBytes = Streams.copy(is, zos);
-            Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes");
-            zos.closeEntry();
-            // Delete old file;
-            boolean deleted = bugreportFile.delete();
+            addEntry(zos, info.bugreportFile.getName(), is);
+            // Delete old file
+            final boolean deleted = info.bugreportFile.delete();
             if (deleted) {
                 Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
             } else {
                 Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
             }
-            return bugreportZippedFile;
+            info.bugreportFile = bugreportZippedFile;
         } catch (IOException e) {
             Log.e(TAG, "exception zipping file " + zippedPath, e);
-            return bugreportFile; // Return original.
         }
     }
 
     /**
+     * Adds the user-provided info into the bugreport zip file.
+     * <p>
+     * If user provided a title, it will be saved into a {@code title.txt} entry; similarly, the
+     * description will be saved on {@code description.txt}.
+     */
+    private void addDetailsToZipFile(BugreportInfo info) {
+        // It's not possible to add a new entry into an existing file, so we need to create a new
+        // zip, copy all entries, then rename it.
+        final File dir = info.bugreportFile.getParentFile();
+        final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
+        Log.d(TAG, "Writing temporary zip file (" + tmpZip + ")");
+        try (ZipFile oldZip = new ZipFile(info.bugreportFile);
+                ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
+
+            // First copy contents from original zip.
+            Enumeration<? extends ZipEntry> entries = oldZip.entries();
+            while (entries.hasMoreElements()) {
+                final ZipEntry entry = entries.nextElement();
+                final String entryName = entry.getName();
+                if (!entry.isDirectory()) {
+                    addEntry(zos, entryName, entry.getTime(), oldZip.getInputStream(entry));
+                } else {
+                    Log.w(TAG, "skipping directory entry: " + entryName);
+                }
+            }
+
+            // Then add the user-provided info.
+            addEntry(zos, "title.txt", info.title);
+            addEntry(zos, "description.txt", info.description);
+        } catch (IOException e) {
+            Log.e(TAG, "exception zipping file " + tmpZip, e);
+            return;
+        }
+
+        if (!tmpZip.renameTo(info.bugreportFile)) {
+            Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile);
+        }
+    }
+
+    private static void addEntry(ZipOutputStream zos, String entry, String text)
+            throws IOException {
+        if (DEBUG) Log.v(TAG, "adding entry '" + entry + "': " + text);
+        if (!TextUtils.isEmpty(text)) {
+            addEntry(zos, entry, new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)));
+        }
+    }
+
+    private static void addEntry(ZipOutputStream zos, String entryName, InputStream is)
+            throws IOException {
+        addEntry(zos, entryName, System.currentTimeMillis(), is);
+    }
+
+    private static void addEntry(ZipOutputStream zos, String entryName, long timestamp,
+            InputStream is) throws IOException {
+        final ZipEntry entry = new ZipEntry(entryName);
+        entry.setTime(timestamp);
+        zos.putNextEntry(entry);
+        final int totalBytes = Streams.copy(is, zos);
+        if (DEBUG) Log.v(TAG, "size of '" + entryName + "' entry: " + totalBytes + " bytes");
+        zos.closeEntry();
+    }
+
+    /**
      * Find the best matching {@link Account} based on build properties.
      */
     private static Account findSendToAccount(Context context) {
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 8e8924a..d1a07ea 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -60,6 +60,7 @@
 import android.support.test.uiautomator.UiObject;
 import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
 
@@ -103,6 +104,12 @@
     private static final String NAME = "BUG, Y U NO REPORT?";
     private static final String NEW_NAME = "Bug_Forrest_Bug";
     private static final String TITLE = "Wimbugdom Champion 2015";
+
+    private static final String NO_DESCRIPTION = null;
+    private static final String NO_NAME = null;
+    private static final String NO_SCREENSHOT = null;
+    private static final String NO_TITLE = null;
+
     private String mDescription;
 
     private String mPlainTextPath;
@@ -157,8 +164,8 @@
 
         Bundle extras =
                 sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath, mScreenshotPath);
-        assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, NAME, ZIP_FILE,
-                null, 1, true);
+        assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, ZIP_FILE,
+                NAME, NO_TITLE, NO_DESCRIPTION, 1, true);
 
         assertServiceNotRunning();
     }
@@ -174,13 +181,14 @@
 
         Bundle extras =
                 sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath, mScreenshotPath);
-        assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, NAME, ZIP_FILE,
-                null, 2, true);
+        assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, ZIP_FILE,
+                NAME, NO_TITLE, NO_DESCRIPTION, 2, true);
 
         assertServiceNotRunning();
     }
 
-    public void testProgress_changeDetails() throws Exception {
+    public void testProgress_changeDetailsInvalidInput() throws Exception {
+
         resetProperties();
         sendBugreportStarted(1000);
         waitForScreenshotButtonEnabled(true);
@@ -219,8 +227,47 @@
 
         Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath,
                 mScreenshotPath);
-        assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, NEW_NAME, TITLE,
-                mDescription, 1, true);
+        assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, TITLE,
+                NEW_NAME, TITLE, mDescription, 1, true);
+
+        assertServiceNotRunning();
+    }
+
+    public void testProgress_changeDetailsPlainBugreport() throws Exception {
+        changeDetailsTest(true);
+    }
+
+    public void testProgress_changeDetailsZippedBugreport() throws Exception {
+        changeDetailsTest(false);
+    }
+
+    public void changeDetailsTest(boolean plainText) throws Exception {
+
+        resetProperties();
+        sendBugreportStarted(1000);
+        waitForScreenshotButtonEnabled(true);
+
+        DetailsUi detailsUi = new DetailsUi(mUiBot);
+
+        // Check initial name.
+        String actualName = detailsUi.nameField.getText().toString();
+        assertEquals("Wrong value on field 'name'", NAME, actualName);
+
+        // Change fields.
+        detailsUi.reOpen();
+        detailsUi.nameField.setText(NEW_NAME);
+        detailsUi.titleField.setText(TITLE);
+        detailsUi.descField.setText(mDescription);
+
+        detailsUi.clickOk();
+
+        assertPropertyValue(NAME_PROPERTY, NEW_NAME);
+        assertProgressNotification(NEW_NAME, "0.00%");
+
+        Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID,
+                plainText? mPlainTextPath : mZipPath, mScreenshotPath);
+        assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, TITLE,
+                NEW_NAME, TITLE, mDescription, 1, true);
 
         assertServiceNotRunning();
     }
@@ -270,8 +317,8 @@
 
         // Finally, share bugreport.
         Bundle extras = acceptBugreportAndGetSharedIntent();
-        assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, NAME, TITLE,
-                mDescription, 1, waitScreenshot);
+        assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, TITLE,
+                NAME, TITLE, mDescription, 1, waitScreenshot);
 
         assertServiceNotRunning();
     }
@@ -296,7 +343,7 @@
         // Share the bugreport.
         mUiBot.chooseActivity(UI_NAME);
         Bundle extras = mListener.getExtras();
-        assertActionSendMultiple(extras, BUGREPORT_CONTENT, null);
+        assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT);
 
         // Make sure it's hidden now.
         int newState = BugreportPrefs.getWarningState(mContext, BugreportPrefs.STATE_UNKNOWN);
@@ -314,13 +361,13 @@
     }
 
     public void testBugreportFinished_plainBugreportAndNoScreenshot() throws Exception {
-        Bundle extras = sendBugreportFinishedAndGetSharedIntent(mPlainTextPath, null);
-        assertActionSendMultiple(extras, BUGREPORT_CONTENT, null);
+        Bundle extras = sendBugreportFinishedAndGetSharedIntent(mPlainTextPath, NO_SCREENSHOT);
+        assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT);
     }
 
     public void testBugreportFinished_zippedBugreportAndNoScreenshot() throws Exception {
-        Bundle extras = sendBugreportFinishedAndGetSharedIntent(mZipPath, null);
-        assertActionSendMultiple(extras, BUGREPORT_CONTENT, null);
+        Bundle extras = sendBugreportFinishedAndGetSharedIntent(mZipPath, NO_SCREENSHOT);
+        assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT);
     }
 
     private void cancelExistingNotifications() {
@@ -426,8 +473,8 @@
      */
     private void assertActionSendMultiple(Bundle extras, String bugreportContent,
             String screenshotContent) throws IOException {
-        assertActionSendMultiple(extras, bugreportContent, screenshotContent, PID, null, ZIP_FILE,
-                null, 0, false);
+        assertActionSendMultiple(extras, bugreportContent, screenshotContent, PID, ZIP_FILE,
+                NO_NAME, NO_TITLE, NO_DESCRIPTION, 0, false);
     }
 
     /**
@@ -437,14 +484,16 @@
      * @param bugreportContent expected content in the bugreport file
      * @param screenshotContent expected content in the screenshot file (sent by dumpstate), if any
      * @param pid emulated dumpstate pid
-     * @param name bugreport name as provided by the user
-     * @param title bugreport name as provided by the user (or received by dumpstate)
+     * @param name expected subject
+     * @param name bugreport name as provided by the user (or received by dumpstate)
+     * @param title bugreport name as provided by the user
      * @param description bugreport description as provided by the user
      * @param numberScreenshots expected number of screenshots taken by Shell.
      * @param renamedScreenshots whether the screenshots are expected to be renamed
      */
     private void assertActionSendMultiple(Bundle extras, String bugreportContent,
-            String screenshotContent, int pid, String name, String title, String description,
+            String screenshotContent, int pid, String subject,
+            String name, String title, String description,
             int numberScreenshots, boolean renamedScreenshots) throws IOException {
         String body = extras.getString(Intent.EXTRA_TEXT);
         assertContainsRegex("missing build info",
@@ -455,7 +504,7 @@
             assertContainsRegex("missing description", description, body);
         }
 
-        assertEquals("wrong subject", title, extras.getString(Intent.EXTRA_SUBJECT));
+        assertEquals("wrong subject", subject, extras.getString(Intent.EXTRA_SUBJECT));
 
         List<Uri> attachments = extras.getParcelableArrayList(Intent.EXTRA_STREAM);
         int expectedNumberScreenshots = numberScreenshots;
@@ -478,6 +527,12 @@
         }
         assertNotNull("did not get .zip attachment", zipUri);
         assertZipContent(zipUri, BUGREPORT_FILE, BUGREPORT_CONTENT);
+        if (!TextUtils.isEmpty(title)) {
+            assertZipContent(zipUri, "title.txt", title);
+        }
+        if (!TextUtils.isEmpty(description)) {
+            assertZipContent(zipUri, "description.txt", description);
+        }
 
         // URI of the screenshot taken by dumpstate.
         Uri externalScreenshotUri = null;
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 6a10c2c..bc18221 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -10,6 +10,7 @@
   public void setGlowScale(float);
 }
 
+-keep class com.android.systemui.statusbar.car.CarStatusBar
 -keep class com.android.systemui.statusbar.phone.PhoneStatusBar
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
 
diff --git a/packages/SystemUI/res/layout/car_navigation_bar.xml b/packages/SystemUI/res/layout/car_navigation_bar.xml
new file mode 100644
index 0000000..f7f673d
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_navigation_bar.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2016, 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.
+*/
+-->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:background="@drawable/system_bar_background">
+
+    <!-- phone.NavigationBarView has rot0 and rot90 but we expect the car head unit to have a fixed
+         rotation so skip this level of the heirarchy.
+    -->
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="horizontal"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:id="@+id/nav_buttons"
+        android:animateLayoutChanges="true">
+
+        <!-- Buttons get populated here from a car_arrays.xml. -->
+    </LinearLayout>
+
+    <!-- lights out layout to match exactly -->
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="horizontal"
+        android:id="@+id/lights_out"
+        android:visibility="gone">
+        <!-- Must match nav_buttons. -->
+    </LinearLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml
new file mode 100644
index 0000000..460433e
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/keyboard_shortcuts_wrapper"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginTop="40dp"
+    android:focusable="true">
+</RelativeLayout>
diff --git a/packages/SystemUI/res/values/arrays_car.xml b/packages/SystemUI/res/values/arrays_car.xml
new file mode 100644
index 0000000..230479d
--- /dev/null
+++ b/packages/SystemUI/res/values/arrays_car.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2015, 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.
+*/
+-->
+<resources>
+    <!-- These should be overriden in an overlay. The default implementation is empty.
+         There needs to be correspondence per index between these arrays, which means that if there
+         isn't a longpress action associated with a shortcut item, put in an empty item to make
+         sure everything lines up.
+    -->
+    <array name="car_shortcut_icons" />
+    <array name="car_shortcut_intent_uris" />
+    <array name="car_shortcut_longpress_intent_uris" />
+</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index c95c73b..ad1ab14 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -145,14 +145,11 @@
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         mStack = stack;
-        // Disable reusing task stack views until the visibility bug is fixed. b/25998134
-        if (false && launchState.launchedReuseTaskStackViews) {
+        if (launchState.launchedReuseTaskStackViews) {
             if (mTaskStackView != null) {
                 // If onRecentsHidden is not triggered, we need to the stack view again here
                 mTaskStackView.reset();
                 mTaskStackView.setStack(stack);
-                removeView(mTaskStackView);
-                addView(mTaskStackView);
             } else {
                 mTaskStackView = new TaskStackView(getContext(), stack);
                 addView(mTaskStackView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 5906bda..6bcf148 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar;
 
-import static  android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MAX;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeInterpolator;
@@ -80,11 +78,8 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
-import android.widget.DateTimeView;
-import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
-import android.widget.SeekBar;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -116,6 +111,7 @@
 import java.util.List;
 import java.util.Locale;
 
+import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MAX;
 import static com.android.keyguard.KeyguardHostView.OnDismissAction;
 
 public abstract class BaseStatusBar extends SystemUI implements
@@ -126,9 +122,6 @@
     public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     public static final boolean MULTIUSER_DEBUG = false;
 
-    // STOPSHIP disable once we resolve b/18102199
-    private static final boolean NOTIFICATION_CLICK_DEBUG = true;
-
     public static final boolean ENABLE_REMOTE_INPUT =
             SystemProperties.getBoolean("debug.enable_remote_input", true);
     public static final boolean ENABLE_CHILD_NOTIFICATIONS
@@ -141,11 +134,9 @@
     protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
     protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
     protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
-    protected static final int MSG_SHOW_KEYBOARD_SHORTCUTS_MENU = 1026;
+    protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
 
     protected static final boolean ENABLE_HEADS_UP = true;
-    // scores above this threshold should be displayed in heads up mode.
-    protected static final int INTERRUPTION_THRESHOLD = 10;
     protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
     // Should match the values in PhoneWindowManager
@@ -200,14 +191,10 @@
     protected IDreamManager mDreamManager;
     PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    protected int mRowMinHeightLegacy;
-    protected int mRowMinHeight;
-    protected int mRowMaxHeight;
 
     // public mode, private notifications, etc
     private boolean mLockscreenPublicMode = false;
     private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
-    private NotificationColorUtil mNotificationColorUtil;
 
     private UserManager mUserManager;
 
@@ -237,6 +224,8 @@
 
     private TimeInterpolator mLinearOutSlowIn, mFastOutLinearIn;
 
+    private KeyboardShortcuts mKeyboardShortcuts;
+
     /**
      * The {@link StatusBarState} of the status bar.
      */
@@ -357,9 +346,6 @@
                 ViewGroup actionGroup = (ViewGroup) parent;
                 index = actionGroup.indexOfChild(view);
             }
-            if (NOTIFICATION_CLICK_DEBUG) {
-                Log.d(TAG, "Clicked on button " + index + " for " + key);
-            }
             try {
                 mBarService.onNotificationActionClick(key, index);
             } catch (RemoteException e) {
@@ -617,8 +603,6 @@
         mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
 
-        mNotificationColorUtil = NotificationColorUtil.getInstance(mContext);
-
         mNotificationData = new NotificationData(this);
 
         mAccessibilityManager = (AccessibilityManager)
@@ -1118,8 +1102,8 @@
     }
 
     @Override
-    public void showKeyboardShortcutsMenu() {
-        int msg = MSG_SHOW_KEYBOARD_SHORTCUTS_MENU;
+    public void toggleKeyboardShortcutsMenu() {
+        int msg = MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU;
         mHandler.removeMessages(msg);
         mHandler.sendEmptyMessage(msg);
     }
@@ -1142,7 +1126,7 @@
          return new H();
     }
 
-    static void sendCloseSystemWindows(Context context, String reason) {
+    protected void sendCloseSystemWindows(String reason) {
         if (ActivityManagerNative.isSystemReady()) {
             try {
                 ActivityManagerNative.getDefault().closeSystemDialogs(reason);
@@ -1177,7 +1161,7 @@
 
     protected void showRecents(boolean triggeredFromAltTab) {
         if (mRecents != null) {
-            sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
+            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
             mRecents.showRecents(triggeredFromAltTab, getStatusBarView());
         }
     }
@@ -1200,8 +1184,8 @@
         }
     }
 
-    protected void showKeyboardShortcuts() {
-        Toast.makeText(mContext, "Show keyboard shortcuts screen", Toast.LENGTH_LONG).show();
+    protected void toggleKeyboardShortcuts() {
+        getKeyboardShortcuts().toggleKeyboardShortcuts(mContext);
     }
 
     protected void cancelPreloadingRecents() {
@@ -1324,8 +1308,8 @@
              case MSG_SHOW_PREV_AFFILIATED_TASK:
                   showRecentsPreviousAffiliatedTask();
                   break;
-             case MSG_SHOW_KEYBOARD_SHORTCUTS_MENU:
-                  showKeyboardShortcuts();
+             case MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU:
+                  toggleKeyboardShortcuts();
                   break;
             }
         }
@@ -1531,6 +1515,14 @@
         }
     }
 
+    protected KeyboardShortcuts getKeyboardShortcuts() {
+        if (mKeyboardShortcuts == null) {
+            mKeyboardShortcuts = new KeyboardShortcuts();
+        }
+
+        return mKeyboardShortcuts;
+    }
+
     public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
         if (!isDeviceProvisioned()) return;
 
@@ -1613,9 +1605,6 @@
                 }
             });
 
-            if (NOTIFICATION_CLICK_DEBUG) {
-                Log.d(TAG, "Clicked on content of " + notificationKey);
-            }
             final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
             final boolean afterKeyguardGone = intent.isActivity()
                     && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index deedae0..5a2758d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -65,7 +65,7 @@
     private static final int MSG_ASSIST_DISCLOSURE          = 22 << MSG_SHIFT;
     private static final int MSG_START_ASSIST               = 23 << MSG_SHIFT;
     private static final int MSG_CAMERA_LAUNCH_GESTURE      = 24 << MSG_SHIFT;
-    private static final int MSG_SHOW_KEYBOARD_SHORTCUTS    = 25 << MSG_SHIFT;
+    private static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS  = 25 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -100,7 +100,7 @@
         public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
         public void toggleRecentApps();
         public void preloadRecentApps();
-        public void showKeyboardShortcutsMenu();
+        public void toggleKeyboardShortcutsMenu();
         public void cancelPreloadRecentApps();
         public void setWindowState(int window, int state);
         public void buzzBeepBlinked();
@@ -229,10 +229,10 @@
     }
 
     @Override
-    public void showKeyboardShortcutsMenu() {
+    public void toggleKeyboardShortcutsMenu() {
         synchronized (mList) {
-            mHandler.removeMessages(MSG_SHOW_KEYBOARD_SHORTCUTS);
-            mHandler.obtainMessage(MSG_SHOW_KEYBOARD_SHORTCUTS).sendToTarget();
+            mHandler.removeMessages(MSG_TOGGLE_KEYBOARD_SHORTCUTS);
+            mHandler.obtainMessage(MSG_TOGGLE_KEYBOARD_SHORTCUTS).sendToTarget();
         }
     }
 
@@ -380,8 +380,8 @@
                 case MSG_CANCEL_PRELOAD_RECENT_APPS:
                     mCallbacks.cancelPreloadRecentApps();
                     break;
-                case MSG_SHOW_KEYBOARD_SHORTCUTS:
-                    mCallbacks.showKeyboardShortcutsMenu();
+                case MSG_TOGGLE_KEYBOARD_SHORTCUTS:
+                    mCallbacks.toggleKeyboardShortcutsMenu();
                     break;
                 case MSG_SET_WINDOW_STATE:
                     mCallbacks.setWindowState(msg.arg1, msg.arg2);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
new file mode 100644
index 0000000..3e0ea90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+
+/**
+ * Contains functionality for handling keyboard shortcuts.
+ */
+public class KeyboardShortcuts {
+    private Dialog mKeyboardShortcutsDialog;
+
+    public KeyboardShortcuts() {}
+
+    public void toggleKeyboardShortcuts(Context context) {
+        if (mKeyboardShortcutsDialog == null) {
+            // Create dialog.
+            AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
+            LayoutInflater inflater = (LayoutInflater) context.getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            final View keyboardShortcutsView = inflater.inflate(
+                    R.layout.keyboard_shortcuts_view, null);
+
+            populateKeyboardShortcuts(keyboardShortcutsView.findViewById(
+                    R.id.keyboard_shortcuts_wrapper));
+            dialogBuilder.setView(keyboardShortcutsView);
+            mKeyboardShortcutsDialog = dialogBuilder.create();
+            mKeyboardShortcutsDialog.setCanceledOnTouchOutside(true);
+
+            // Setup window.
+            Window keyboardShortcutsWindow = mKeyboardShortcutsDialog.getWindow();
+            keyboardShortcutsWindow.setType(
+                    WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+            keyboardShortcutsWindow.setBackgroundDrawable(
+                    new ColorDrawable(android.graphics.Color.TRANSPARENT));
+            keyboardShortcutsWindow.setGravity(Gravity.TOP);
+            mKeyboardShortcutsDialog.show();
+        } else {
+            dismissKeyboardShortcutsDialog();
+        }
+    }
+
+    public void dismissKeyboardShortcutsDialog() {
+        if (mKeyboardShortcutsDialog != null) {
+            mKeyboardShortcutsDialog.dismiss();
+            mKeyboardShortcutsDialog = null;
+        }
+    }
+
+    /**
+     * @return {@code true} if the keyboard shortcuts have been successfully populated.
+     */
+    private boolean populateKeyboardShortcuts(View keyboardShortcutsLayout) {
+        // TODO: Populate shortcuts.
+        return true;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index f243b00..d7e47c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -30,11 +30,11 @@
 public class RemoteInputController {
 
     private final ArrayList<WeakReference<NotificationData.Entry>> mRemoteInputs = new ArrayList<>();
-    private final StatusBarWindowManager mStatusBarWindowManager;
+    private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
     private final HeadsUpManager mHeadsUpManager;
 
     public RemoteInputController(StatusBarWindowManager sbwm, HeadsUpManager headsUpManager) {
-        mStatusBarWindowManager = sbwm;
+        addCallback(sbwm);
         mHeadsUpManager = headsUpManager;
     }
 
@@ -59,8 +59,12 @@
     }
 
     private void apply(NotificationData.Entry entry) {
-        mStatusBarWindowManager.setRemoteInputActive(isRemoteInputActive());
         mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
+        boolean remoteInputActive = isRemoteInputActive();
+        int N = mCallbacks.size();
+        for (int i = 0; i < N; i++) {
+            mCallbacks.get(i).onRemoteInputActive(remoteInputActive);
+        }
     }
 
     /**
@@ -99,4 +103,12 @@
     }
 
 
+    public void addCallback(Callback callback) {
+        Preconditions.checkNotNull(callback);
+        mCallbacks.add(callback);
+    }
+
+    public interface Callback {
+        void onRemoteInputActive(boolean active);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
new file mode 100644
index 0000000..5c0f38c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.car;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.R.color;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.ActivityStarter;
+import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
+import java.net.URISyntaxException;
+
+/**
+ * A custom navigation bar for the automotive use case.
+ * <p>
+ * The navigation bar in the automotive use case is more like a list of shortcuts, which we
+ * expect to be customizable by the car OEMs. This implementation populates the nav_buttons layout
+ * from resources rather than the layout file so customization would then mean updating
+ * arrays_car.xml appropriately in an overlay.
+ */
+class CarNavigationBarView extends NavigationBarView {
+    private ActivityStarter mActivityStarter;
+
+    public CarNavigationBarView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void onFinishInflate() {
+        // Read up arrays_car.xml and populate the navigation bar here.
+        Context context = getContext();
+        Resources r = getContext().getResources();
+        TypedArray icons = r.obtainTypedArray(R.array.car_shortcut_icons);
+        TypedArray intents = r.obtainTypedArray(R.array.car_shortcut_intent_uris);
+        TypedArray longpressIntents =
+                r.obtainTypedArray(R.array.car_shortcut_longpress_intent_uris);
+
+        if (icons.length() != intents.length()) {
+            throw new RuntimeException("car_shortcut_icons and car_shortcut_intents do not match");
+        }
+
+        LinearLayout navButtons = (LinearLayout) findViewById(R.id.nav_buttons);
+        LinearLayout lightsOut = (LinearLayout) findViewById(R.id.lights_out);
+
+        for (int i = 0; i < icons.length(); i++) {
+            Drawable icon = icons.getDrawable(i);
+
+            try {
+                Intent intent = Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME);
+                Intent longpress = null;
+                String longpressUri = longpressIntents.getString(i);
+                if (!longpressUri.isEmpty()) {
+                    longpress = Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME);
+                }
+
+                // nav_buttons and lights_out should match exactly.
+                navButtons.addView(makeButton(context, icon, intent, longpress));
+                lightsOut.addView(makeButton(context, icon, intent, longpress));
+            } catch (URISyntaxException e) {
+                throw new RuntimeException("Malformed intent uri", e);
+            }
+        }
+    }
+
+    private ImageButton makeButton(Context context, Drawable icon,
+            final Intent intent, final Intent longpress) {
+        ImageButton button = new ImageButton(context);
+
+        button.setImageDrawable(icon);
+        button.setScaleType(ScaleType.CENTER);
+        button.setBackgroundColor(color.transparent);
+        LinearLayout.LayoutParams lp =
+                new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
+        button.setLayoutParams(lp);
+
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mActivityStarter != null) {
+                    mActivityStarter.startActivity(intent, true);
+                }
+            }
+        });
+
+        // Long click handlers are optional.
+        if (longpress != null) {
+            button.setLongClickable(true);
+            button.setOnLongClickListener(new OnLongClickListener() {
+                @Override
+                public boolean onLongClick(View v) {
+                    if (mActivityStarter != null) {
+                        mActivityStarter.startActivity(longpress, true);
+                        return true;
+                    }
+                    return false;
+                }
+            });
+        } else {
+            button.setLongClickable(false);
+        }
+
+        return button;
+    }
+
+    public void setActivityStarter(ActivityStarter activityStarter) {
+        mActivityStarter = activityStarter;
+    }
+
+    @Override
+    public void setDisabledFlags(int disabledFlags, boolean force) {
+        // TODO: Populate.
+    }
+
+    @Override
+    public void reorient() {
+        // We expect the car head unit to always have a fixed rotation so we ignore this. The super
+        // class implentation expects mRotatedViews to be populated, so if you call into it, there
+        // is a possibility of a NullPointerException.
+    }
+
+    @Override
+    public View getCurrentView() {
+        return this;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
new file mode 100644
index 0000000..a72b5d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.car;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+/**
+ * A status bar (and navigation bar) tailored for the automotive use case.
+ */
+public class CarStatusBar extends PhoneStatusBar {
+    @Override
+    protected void addNavigationBar() {
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+                    WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                PixelFormat.TRANSLUCENT);
+        lp.setTitle("CarNavigationBar");
+        lp.windowAnimations = 0;
+        mWindowManager.addView(mNavigationBarView, lp);
+    }
+
+    @Override
+    protected void createNavigationBarView(Context context) {
+        if (mNavigationBarView != null) {
+            return;
+        }
+
+        CarNavigationBarView carNavBar =
+                (CarNavigationBarView) View.inflate(context, R.layout.car_navigation_bar, null);
+        carNavBar.setActivityStarter(this);
+        mNavigationBarView = carNavBar;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index a3d0ce6..02812a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -157,8 +157,8 @@
                     final String how = "" + m.obj;
                     final int w = getWidth();
                     final int h = getHeight();
-                    final int vw = mCurrentView.getWidth();
-                    final int vh = mCurrentView.getHeight();
+                    final int vw = getCurrentView().getWidth();
+                    final int vh = getCurrentView().getHeight();
 
                     if (h != vh || w != vw) {
                         Log.w(TAG, String.format(
@@ -230,27 +230,27 @@
     }
 
     public KeyButtonView getRecentsButton() {
-        return (KeyButtonView) mCurrentView.findViewById(R.id.recent_apps);
+        return (KeyButtonView) getCurrentView().findViewById(R.id.recent_apps);
     }
 
     public View getMenuButton() {
-        return mCurrentView.findViewById(R.id.menu);
+        return getCurrentView().findViewById(R.id.menu);
     }
 
     public View getBackButton() {
-        return mCurrentView.findViewById(R.id.back);
+        return getCurrentView().findViewById(R.id.back);
     }
 
     public KeyButtonView getHomeButton() {
-        return (KeyButtonView) mCurrentView.findViewById(R.id.home);
+        return (KeyButtonView) getCurrentView().findViewById(R.id.home);
     }
 
     public View getImeSwitchButton() {
-        return mCurrentView.findViewById(R.id.ime_switcher);
+        return getCurrentView().findViewById(R.id.ime_switcher);
     }
 
     public View getAppShelf() {
-        return mCurrentView.findViewById(R.id.app_shelf);
+        return getCurrentView().findViewById(R.id.app_shelf);
     }
 
     private void getIcons(Resources res) {
@@ -326,7 +326,7 @@
             setSlippery(disableHome && disableRecent && disableBack && disableSearch);
         }
 
-        ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons);
+        ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
         if (navButtons != null) {
             LayoutTransition lt = navButtons.getLayoutTransition();
             if (lt != null) {
@@ -379,7 +379,7 @@
 
     private void updateLayoutTransitionsEnabled() {
         boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled;
-        ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons);
+        ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
         LayoutTransition lt = navButtons.getLayoutTransition();
         if (lt != null) {
             if (enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 42fd872..ba20679 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -1318,7 +1318,6 @@
         mHeader.setExpansion(getHeaderExpansionFraction());
         setQsTranslation(height);
         requestScrollerTopPaddingUpdate(false /* animate */);
-        updateNotificationScrim(height);
         if (mKeyguardShowing) {
             updateHeaderKeyguard();
         }
@@ -1355,12 +1354,6 @@
         }
     }
 
-    private void updateNotificationScrim(float height) {
-        int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
-        float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
-        progress = Math.max(0.0f, Math.min(progress, 1.0f));
-    }
-
     private float getHeaderExpansionFraction() {
         if (!mKeyguardShowing) {
             return getQsExpansionFraction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index e1a400d..6aa072f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -71,7 +71,6 @@
             Log.e(TAG, "setPanelHolder: null PanelHolder", new Throwable());
             return;
         }
-        ph.setBar(this);
         mPanelHolder = ph;
         final int N = ph.getChildCount();
         for (int i=0; i<N; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java
index d7f34d5..5095ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java
@@ -28,7 +28,6 @@
     public static final boolean DEBUG_GESTURES = true;
 
     private int mSelectedPanelIndex = -1;
-    private PanelBar mBar;
 
     public PanelHolder(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -79,8 +78,4 @@
         }
         return false;
     }
-
-    public void setBar(PanelBar panelBar) {
-        mBar = panelBar;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 5e54ba7..7b2498f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -58,7 +58,6 @@
 
     private float mPeekHeight;
     private float mHintDistance;
-    private int mEdgeTapAreaWidth;
     private float mInitialOffsetOnTouch;
     private boolean mCollapsedAndHeadsUpOnDown;
     private float mExpandedFraction = 0;
@@ -202,7 +201,6 @@
         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
         mTouchSlop = configuration.getScaledTouchSlop();
         mHintDistance = res.getDimension(R.dimen.hint_move_distance);
-        mEdgeTapAreaWidth = res.getDimensionPixelSize(R.dimen.edge_tap_area_width);
         mUnlockFalsingThreshold = res.getDimensionPixelSize(R.dimen.unlock_falsing_threshold);
     }
 
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 78d09e3..d688250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -728,32 +728,7 @@
             boolean showNav = mWindowManagerService.hasNavigationBar();
             if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
             if (showNav) {
-                // Optionally show app shortcuts in the nav bar "shelf" area.
-                if (shouldShowAppShelf()) {
-                    mNavigationBarView = (NavigationBarView) View.inflate(
-                            context, R.layout.navigation_bar_with_apps, null);
-                } else {
-                    mNavigationBarView = (NavigationBarView) View.inflate(
-                            context, R.layout.navigation_bar, null);
-                }
-                mNavigationBarView.setDisabledFlags(mDisabled1);
-                mNavigationBarView.setComponents(mRecents, getComponent(Divider.class));
-                mNavigationBarView.setOnVerticalChangedListener(
-                        new NavigationBarView.OnVerticalChangedListener() {
-                    @Override
-                    public void onVerticalChanged(boolean isVertical) {
-                        if (mAssistManager != null) {
-                            mAssistManager.onConfigurationChanged();
-                        }
-                        mNotificationPanel.setQsScrimEnabled(!isVertical);
-                    }
-                });
-                mNavigationBarView.setOnTouchListener(new View.OnTouchListener() {
-                    @Override
-                    public boolean onTouch(View v, MotionEvent event) {
-                        checkUserAutohide(v, event);
-                        return false;
-                    }});
+                createNavigationBarView(context);
             }
         } catch (RemoteException ex) {
             // no window manager? good luck with that
@@ -979,6 +954,35 @@
         return mStatusBarView;
     }
 
+    protected void createNavigationBarView(Context context) {
+    // Optionally show app shortcuts in the nav bar "shelf" area.
+        if (shouldShowAppShelf()) {
+            mNavigationBarView = (NavigationBarView) View.inflate(
+                    context, R.layout.navigation_bar_with_apps, null);
+        } else {
+            mNavigationBarView = (NavigationBarView) View.inflate(
+                    context, R.layout.navigation_bar, null);
+        }
+        mNavigationBarView.setDisabledFlags(mDisabled1);
+        mNavigationBarView.setComponents(mRecents, getComponent(Divider.class));
+        mNavigationBarView.setOnVerticalChangedListener(
+                new NavigationBarView.OnVerticalChangedListener() {
+            @Override
+            public void onVerticalChanged(boolean isVertical) {
+                if (mAssistManager != null) {
+                    mAssistManager.onConfigurationChanged();
+                }
+                mNotificationPanel.setQsScrimEnabled(!isVertical);
+            }
+        });
+        mNavigationBarView.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                checkUserAutohide(v, event);
+                return false;
+            }});
+    }
+
     /** Returns true if the app shelf should be shown in the nav bar. */
     private boolean shouldShowAppShelf() {
         // Allow adb to override the default shelf behavior:
@@ -1086,6 +1090,7 @@
         mKeyguardIndicationController.setStatusBarKeyguardViewManager(
                 mStatusBarKeyguardViewManager);
         mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+        mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
         mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback();
     }
 
@@ -1191,7 +1196,7 @@
     }
 
     // For small-screen devices (read: phones) that lack hardware navigation buttons
-    private void addNavigationBar() {
+    protected void addNavigationBar() {
         if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mNavigationBarView);
         if (mNavigationBarView == null) return;
 
@@ -2987,6 +2992,7 @@
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
             String action = intent.getAction();
             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+                getKeyboardShortcuts().dismissKeyboardShortcutsDialog();
                 if (isCurrentProfile(getSendingUserId())) {
                     int flags = CommandQueue.FLAG_EXCLUDE_NONE;
                     String reason = intent.getStringExtra("reason");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c0887ca..ab37e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -51,7 +51,6 @@
     public PhoneStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        Resources res = getContext().getResources();
         mBarTransitions = new PhoneStatusBarTransitions(this);
     }
 
@@ -102,7 +101,6 @@
 
     @Override
     public PanelView selectPanelForTouch(MotionEvent touch) {
-        // No double swiping. If either panel is open, nothing else can be pulled down.
         return mNotificationPanel.getExpandedHeight() > 0
                 ? null
                 : mNotificationPanel;
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 05f6e57..f14f0d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -31,6 +31,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.RemoteInputController;
 
 import static com.android.keyguard.KeyguardHostView.OnDismissAction;
 
@@ -40,7 +41,7 @@
  * which is in turn, reported to this class by the current
  * {@link com.android.keyguard.KeyguardViewBase}.
  */
-public class StatusBarKeyguardViewManager {
+public class StatusBarKeyguardViewManager implements RemoteInputController.Callback {
 
     // When hiding the Keyguard with timing supplied from WindowManager, better be early than late.
     private static final long HIDE_TIMING_CORRECTION_MS = -3 * 16;
@@ -69,12 +70,15 @@
     private KeyguardBouncer mBouncer;
     private boolean mShowing;
     private boolean mOccluded;
+    private boolean mRemoteInputActive;
 
     private boolean mFirstUpdate = true;
     private boolean mLastShowing;
     private boolean mLastOccluded;
     private boolean mLastBouncerShowing;
     private boolean mLastBouncerDismissible;
+    private boolean mLastRemoteInputActive;
+
     private OnDismissAction mAfterKeyguardGoneAction;
     private boolean mDeviceWillWakeUp;
     private boolean mDeferScrimFadeOut;
@@ -199,6 +203,12 @@
         mPhoneStatusBar.onScreenTurnedOn();
     }
 
+    @Override
+    public void onRemoteInputActive(boolean active) {
+        mRemoteInputActive = active;
+        updateStates();
+    }
+
     public void onScreenTurnedOff() {
         mScreenTurnedOn = false;
     }
@@ -429,18 +439,21 @@
         boolean occluded = mOccluded;
         boolean bouncerShowing = mBouncer.isShowing();
         boolean bouncerDismissible = !mBouncer.isFullscreenBouncer();
+        boolean remoteInputActive = mRemoteInputActive;
 
-        if ((bouncerDismissible || !showing) != (mLastBouncerDismissible || !mLastShowing)
+        if ((bouncerDismissible || !showing || remoteInputActive) !=
+                (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
                 || mFirstUpdate) {
-            if (bouncerDismissible || !showing) {
+            if (bouncerDismissible || !showing || remoteInputActive) {
                 mContainer.setSystemUiVisibility(vis & ~View.STATUS_BAR_DISABLE_BACK);
             } else {
                 mContainer.setSystemUiVisibility(vis | View.STATUS_BAR_DISABLE_BACK);
             }
         }
 
-        boolean navBarVisible = (!(showing && !occluded) || bouncerShowing);
-        boolean lastNavBarVisible = (!(mLastShowing && !mLastOccluded) || mLastBouncerShowing);
+        boolean navBarVisible = (!(showing && !occluded) || bouncerShowing || remoteInputActive);
+        boolean lastNavBarVisible = (!(mLastShowing && !mLastOccluded) || mLastBouncerShowing
+                || mLastRemoteInputActive);
         if (navBarVisible != lastNavBarVisible || mFirstUpdate) {
             if (mPhoneStatusBar.getNavigationBarView() != null) {
                 if (navBarVisible) {
@@ -477,6 +490,7 @@
         mLastOccluded = occluded;
         mLastBouncerShowing = bouncerShowing;
         mLastBouncerDismissible = bouncerDismissible;
+        mLastRemoteInputActive = remoteInputActive;
 
         mPhoneStatusBar.onKeyguardViewManagerStatesUpdated();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index abe51ac..9d2f0de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -30,6 +30,7 @@
 import com.android.keyguard.R;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 
 import java.io.FileDescriptor;
@@ -39,7 +40,7 @@
 /**
  * Encapsulates all logic for the status bar window state management.
  */
-public class StatusBarWindowManager {
+public class StatusBarWindowManager implements RemoteInputController.Callback {
 
     private final Context mContext;
     private final WindowManager mWindowManager;
@@ -292,7 +293,8 @@
         apply(mCurrentState);
     }
 
-    public void setRemoteInputActive(boolean remoteInputActive) {
+    @Override
+    public void onRemoteInputActive(boolean remoteInputActive) {
         mCurrentState.remoteInputActive = remoteInputActive;
         apply(mCurrentState);
     }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index d5c3113..745f476 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -767,7 +767,7 @@
                 synchronized (mLock) {
                     // If we don't have a media button receiver to fall back on
                     // include non-playing sessions for dispatching
-                    UserRecord ur = mUserRecords.get(ActivityManager.getCurrentUser());
+                    UserRecord ur = mUserRecords.get(mCurrentUserId);
                     boolean useNotPlayingSessions = (ur == null) ||
                             (ur.mLastMediaButtonReceiver == null
                                 && ur.mRestoredMediaButtonReceiver == null);
@@ -949,8 +949,7 @@
                         mKeyEventReceiver);
             } else {
                 // Launch the last PendingIntent we had with priority
-                int userId = ActivityManager.getCurrentUser();
-                UserRecord user = mUserRecords.get(userId);
+                UserRecord user = mUserRecords.get(mCurrentUserId);
                 if (user != null && (user.mLastMediaButtonReceiver != null
                         || user.mRestoredMediaButtonReceiver != null)) {
                     if (DEBUG) {
@@ -967,11 +966,11 @@
                         if (user.mLastMediaButtonReceiver != null) {
                             user.mLastMediaButtonReceiver.send(getContext(),
                                     needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
-                                    mediaButtonIntent, mKeyEventReceiver, null);
+                                    mediaButtonIntent, mKeyEventReceiver, mHandler);
                         } else {
                             mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver);
                             getContext().sendBroadcastAsUser(mediaButtonIntent,
-                                    new UserHandle(userId));
+                                    new UserHandle(mCurrentUserId));
                         }
                     } catch (CanceledException e) {
                         Log.i(TAG, "Error sending key event to media button receiver "
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 72611b7..4fbc030 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2879,7 +2879,7 @@
         } else if (keyCode == KeyEvent.KEYCODE_SLASH && event.isMetaPressed()) {
             if (down) {
                 if (repeatCount == 0) {
-                    showKeyboardShortcutsMenu();
+                    toggleKeyboardShortcutsMenu();
                 }
             }
         } else if (keyCode == KeyEvent.KEYCODE_ASSIST) {
@@ -3311,11 +3311,11 @@
         }
     }
 
-    private void showKeyboardShortcutsMenu() {
+    private void toggleKeyboardShortcutsMenu() {
         try {
             IStatusBarService statusbar = getStatusBarService();
             if (statusbar != null) {
-                statusbar.showKeyboardShortcutsMenu();
+                statusbar.toggleKeyboardShortcutsMenu();
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "RemoteException when showing keyboard shortcuts menu", e);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index fc27170..2d38da5 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -503,10 +503,10 @@
     }
 
     @Override
-    public void showKeyboardShortcutsMenu() {
+    public void toggleKeyboardShortcutsMenu() {
         if (mBar != null) {
             try {
-                mBar.showKeyboardShortcutsMenu();
+                mBar.toggleKeyboardShortcutsMenu();
             } catch (RemoteException ex) {}
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 53edc789..dd58b3c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -129,6 +129,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
+import com.android.server.pm.UserManagerService;
 import com.android.server.pm.UserRestrictionsUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -426,6 +427,7 @@
         private static final String TAG_USER_RESTRICTIONS = "user-restrictions";
         private static final String TAG_SHORT_SUPPORT_MESSAGE = "short-support-message";
         private static final String TAG_LONG_SUPPORT_MESSAGE = "long-support-message";
+        private static final String TAG_PARENT_ADMIN = "parent-admin";
 
         final DeviceAdminInfo info;
 
@@ -478,6 +480,9 @@
         boolean disableScreenCapture = false; // Can only be set by a device/profile owner.
         boolean requireAutoTime = false; // Can only be set by a device owner.
 
+        ActiveAdmin parentAdmin;
+        final boolean isParent;
+
         static class TrustAgentInfo {
             public PersistableBundle options;
             TrustAgentInfo(PersistableBundle bundle) {
@@ -515,8 +520,16 @@
         String shortSupportMessage = null;
         String longSupportMessage = null;
 
-        ActiveAdmin(DeviceAdminInfo _info) {
+        ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
             info = _info;
+            isParent = parent;
+        }
+
+        ActiveAdmin getParentActiveAdmin() {
+            if (parentAdmin == null && !isParent) {
+                parentAdmin = new ActiveAdmin(info, /* parent */ true);
+            }
+            return parentAdmin;
         }
 
         int getUid() { return info.getActivityInfo().applicationInfo.uid; }
@@ -704,6 +717,11 @@
                 out.text(longSupportMessage);
                 out.endTag(null, TAG_LONG_SUPPORT_MESSAGE);
             }
+            if (parentAdmin != null) {
+                out.startTag(null, TAG_PARENT_ADMIN);
+                parentAdmin.writeToXml(out);
+                out.endTag(null, TAG_PARENT_ADMIN);
+            }
         }
 
         void writePackageListToXml(XmlSerializer out, String outerTag,
@@ -831,6 +849,9 @@
                     } else {
                         Log.w(LOG_TAG, "Missing text when loading long support message");
                     }
+                } else if (TAG_PARENT_ADMIN.equals(tag)) {
+                    parentAdmin = new ActiveAdmin(info, /* parent */ true);
+                    parentAdmin.readFromXml(parser);
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -2014,7 +2035,7 @@
                                     + userHandle);
                         }
                         if (dai != null) {
-                            ActiveAdmin ap = new ActiveAdmin(dai);
+                            ActiveAdmin ap = new ActiveAdmin(dai, /* parent */ false);
                             ap.readFromXml(parser);
                             policy.mAdminMap.put(ap.info.getComponent(), ap);
                         }
@@ -2405,7 +2426,7 @@
                         && getActiveAdminUncheckedLocked(adminReceiver, userHandle) != null) {
                     throw new IllegalArgumentException("Admin is already added");
                 }
-                ActiveAdmin newAdmin = new ActiveAdmin(info);
+                ActiveAdmin newAdmin = new ActiveAdmin(info, /* parent */ false);
                 policy.mAdminMap.put(adminReceiver, newAdmin);
                 int replaceIndex = -1;
                 final int N = policy.mAdminList.size();
@@ -2540,8 +2561,14 @@
         }
     }
 
+    private boolean isAdminApiLevelPreN(@NonNull ComponentName who, int userHandle) {
+        DeviceAdminInfo adminInfo = findAdmin(who, userHandle, false);
+        return adminInfo.getActivityInfo().applicationInfo.targetSdkVersion
+                < Build.VERSION_CODES.N;
+    }
+
     @Override
-    public void setPasswordQuality(ComponentName who, int quality) {
+    public void setPasswordQuality(ComponentName who, int quality, boolean parent) {
         if (!mHasFeature) {
             return;
         }
@@ -2552,6 +2579,9 @@
         synchronized (this) {
             ActiveAdmin ap = getActiveAdminForCallerLocked(who,
                     DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+            if (parent) {
+                ap = ap.getParentActiveAdmin();
+            }
             if (ap.passwordQuality != quality) {
                 ap.passwordQuality = quality;
                 saveSettingsLocked(userHandle);
@@ -2560,7 +2590,7 @@
     }
 
     @Override
-    public int getPasswordQuality(ComponentName who, int userHandle) {
+    public int getPasswordQuality(ComponentName who, int userHandle, boolean parent) {
         if (!mHasFeature) {
             return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
         }
@@ -2570,20 +2600,43 @@
 
             if (who != null) {
                 ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
+                if (parent && admin != null) {
+                    admin = admin.getParentActiveAdmin();
+                }
                 return admin != null ? admin.passwordQuality : mode;
             }
 
-            // Return strictest policy for this user and profiles that are visible from this user.
-            List<UserInfo> profiles = mUserManager.getProfiles(userHandle);
-            for (UserInfo userInfo : profiles) {
-                DevicePolicyData policy = getUserDataUnchecked(userInfo.id);
+            if (LockPatternUtils.isSeparateWorkChallengeEnabled() && !parent) {
+                // If a Work Challenge is in use, only return its restrictions.
+                DevicePolicyData policy = getUserDataUnchecked(userHandle);
                 final int N = policy.mAdminList.size();
-                for (int i=0; i<N; i++) {
+                for (int i = 0; i < N; i++) {
                     ActiveAdmin admin = policy.mAdminList.get(i);
                     if (mode < admin.passwordQuality) {
                         mode = admin.passwordQuality;
                     }
                 }
+            } else {
+                // Return strictest policy for this user and profiles that are visible from this
+                // user that do not use a separate work challenge.
+                // TODO: When there are separate parent restrictions the profile should just
+                // obey its own.
+                List<UserInfo> profiles = mUserManager.getProfiles(userHandle);
+                for (UserInfo userInfo : profiles) {
+                    // Only aggregate data for the parent profile plus the non-work challenge
+                    // enabled profiles.
+                    if (!(userInfo.isManagedProfile()
+                            && LockPatternUtils.isSeparateWorkChallengeEnabled())) {
+                        DevicePolicyData policy = getUserDataUnchecked(userInfo.id);
+                        final int N = policy.mAdminList.size();
+                        for (int i = 0; i < N; i++) {
+                            ActiveAdmin admin = policy.mAdminList.get(i);
+                            if (mode < admin.passwordQuality) {
+                                mode = admin.passwordQuality;
+                            }
+                        }
+                    }
+                }
             }
             return mode;
         }
@@ -3143,7 +3196,7 @@
     }
 
     @Override
-    public boolean isActivePasswordSufficient(int userHandle) {
+    public boolean isActivePasswordSufficient(int userHandle, boolean parent) {
         if (!mHasFeature) {
             return true;
         }
@@ -3155,8 +3208,14 @@
 
             // This API can only be called by an active device admin,
             // so try to retrieve it to check that the caller is one.
-            getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
-            if (policy.mActivePasswordQuality < getPasswordQuality(null, userHandle)
+            ActiveAdmin admin =
+                    getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+            ComponentName adminComponentName = admin.info.getComponent();
+            // TODO: Include the Admin sdk level check in LockPatternUtils check.
+            ComponentName who = !isAdminApiLevelPreN(adminComponentName, userHandle)
+                    && LockPatternUtils.isSeparateWorkChallengeEnabled()
+                        ? adminComponentName : null;
+            if (policy.mActivePasswordQuality < getPasswordQuality(who, userHandle, parent)
                     || policy.mActivePasswordLength < getPasswordMinimumLength(null, userHandle)) {
                 return false;
             }
@@ -3321,7 +3380,7 @@
                     }
                 }
             }
-            quality = getPasswordQuality(null, userHandle);
+            quality = getPasswordQuality(null, userHandle, false);
             if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
                 int realQuality = LockPatternUtils.computePasswordQuality(password);
                 if (realQuality < quality
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index d5f384d..d250739 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -24,7 +24,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.graphics.drawable.Icon;
@@ -56,7 +55,6 @@
 import java.io.PrintWriter;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
 
 /**
  * SystemService wrapper for the PrintManager implementation. Publishes
@@ -88,11 +86,6 @@
     }
 
     class PrintManagerImpl extends IPrintManager.Stub {
-        private static final char COMPONENT_NAME_SEPARATOR = ':';
-
-        private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME =
-                "EXTRA_PRINT_SERVICE_COMPONENT_NAME";
-
         private static final int BACKGROUND_USER_ID = -10;
 
         private final Object mLock = new Object();
@@ -487,7 +480,7 @@
 
         private void registerContentObservers() {
             final Uri enabledPrintServicesUri = Settings.Secure.getUriFor(
-                    Settings.Secure.ENABLED_PRINT_SERVICES);
+                    Settings.Secure.DISABLED_PRINT_SERVICES);
             ContentObserver observer = new ContentObserver(BackgroundThread.getHandler()) {
                 @Override
                 public void onChange(boolean selfChange, Uri uri, int userId) {
@@ -511,8 +504,7 @@
 
         private void registerBroadcastReceivers() {
             PackageMonitor monitor = new PackageMonitor() {
-                @Override
-                public void onPackageModified(String packageName) {
+                private void updateServices(String packageName) {
                     if (!mUserManager.isUserUnlocked(getChangingUserId())) return;
                     synchronized (mLock) {
                         // A background user/profile's print jobs are running but there is
@@ -520,11 +512,15 @@
                         // to handle it as the change may affect ongoing print jobs.
                         boolean servicesChanged = false;
                         UserState userState = getOrCreateUserStateLocked(getChangingUserId());
-                        Iterator<ComponentName> iterator = userState.getEnabledServices().iterator();
-                        while (iterator.hasNext()) {
-                            ComponentName componentName = iterator.next();
-                            if (packageName.equals(componentName.getPackageName())) {
+
+                        List<PrintServiceInfo> installedServices = userState
+                                .getInstalledPrintServices();
+                        final int numInstalledServices = installedServices.size();
+                        for (int i = 0; i < numInstalledServices; i++) {
+                            if (installedServices.get(i).getResolveInfo().serviceInfo.packageName
+                                    .equals(packageName)) {
                                 servicesChanged = true;
+                                break;
                             }
                         }
                         if (servicesChanged) {
@@ -534,30 +530,15 @@
                 }
 
                 @Override
+                public void onPackageModified(String packageName) {
+                    updateServices(packageName);
+                    getOrCreateUserStateLocked(getChangingUserId()).prunePrintServices();
+                }
+
+                @Override
                 public void onPackageRemoved(String packageName, int uid) {
-                    if (!mUserManager.isUserUnlocked(getChangingUserId())) return;
-                    synchronized (mLock) {
-                        // A background user/profile's print jobs are running but there is
-                        // no UI shown. Hence, if the packages of such a user change we need
-                        // to handle it as the change may affect ongoing print jobs.
-                        boolean servicesRemoved = false;
-                        UserState userState = getOrCreateUserStateLocked(getChangingUserId());
-                        Iterator<ComponentName> iterator = userState.getEnabledServices().iterator();
-                        while (iterator.hasNext()) {
-                            ComponentName componentName = iterator.next();
-                            if (packageName.equals(componentName.getPackageName())) {
-                                userState.removeApprovedPrintService(componentName);
-                                iterator.remove();
-                                servicesRemoved = true;
-                            }
-                        }
-                        if (servicesRemoved) {
-                            persistComponentNamesToSettingLocked(
-                                    Settings.Secure.ENABLED_PRINT_SERVICES,
-                                    userState.getEnabledServices(), getChangingUserId());
-                            userState.updateIfNeededLocked();
-                        }
-                    }
+                    updateServices(packageName);
+                    getOrCreateUserStateLocked(getChangingUserId()).prunePrintServices();
                 }
 
                 @Override
@@ -570,10 +551,10 @@
                         // to handle it as the change may affect ongoing print jobs.
                         UserState userState = getOrCreateUserStateLocked(getChangingUserId());
                         boolean stoppedSomePackages = false;
-                        Iterator<ComponentName> iterator = userState.getEnabledServices()
+                        Iterator<PrintServiceInfo> iterator = userState.getEnabledPrintServices()
                                 .iterator();
                         while (iterator.hasNext()) {
-                            ComponentName componentName = iterator.next();
+                            ComponentName componentName = iterator.next().getComponentName();
                             String componentPackage = componentName.getPackageName();
                             for (String stoppedPackage : stoppedPackages) {
                                 if (componentPackage.equals(stoppedPackage)) {
@@ -606,48 +587,11 @@
                             .queryIntentServicesAsUser(intent, PackageManager.GET_SERVICES,
                                     getChangingUserId());
 
-                    if (installedServices == null) {
-                        return;
-                    }
-
-                    // Enable all added services by default
-                    synchronized (mLock) {
+                    if (installedServices != null) {
                         UserState userState = getOrCreateUserStateLocked(getChangingUserId());
-
-                        Set<ComponentName> enabledServices = userState.getEnabledServices();
-                        boolean servicesAdded = false;
-
-                        final int installedServiceCount = installedServices.size();
-                        for (int i = 0; i < installedServiceCount; i++) {
-                            ServiceInfo serviceInfo = installedServices.get(i).serviceInfo;
-                            ComponentName component = new ComponentName(serviceInfo.packageName,
-                                    serviceInfo.name);
-
-                            enabledServices.add(component);
-                            servicesAdded = true;
-                        }
-
-                        if (servicesAdded) {
-                            persistComponentNamesToSettingLocked(
-                                    Settings.Secure.ENABLED_PRINT_SERVICES, enabledServices,
-                                    getChangingUserId());
-                            userState.updateIfNeededLocked();
-                        }
+                        userState.updateIfNeededLocked();
                     }
                 }
-
-                private void persistComponentNamesToSettingLocked(String settingName,
-                        Set<ComponentName> componentNames, int userId) {
-                    StringBuilder builder = new StringBuilder();
-                    for (ComponentName componentName : componentNames) {
-                        if (builder.length() > 0) {
-                            builder.append(COMPONENT_NAME_SEPARATOR);
-                        }
-                        builder.append(componentName.flattenToShortString());
-                    }
-                    Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                            settingName, builder.toString(), userId);
-                }
             };
 
             // package changes
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index 40a8880..d179b95 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -37,17 +37,18 @@
 import android.print.PrintJobId;
 import android.print.PrintJobInfo;
 import android.print.PrinterId;
+import android.printservice.PrintService;
 import android.util.Slog;
 import android.util.TimedRemoteCaller;
 
+import libcore.io.IoUtils;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 
-import libcore.io.IoUtils;
-
 /**
  * This represents the remote print spooler as a local object to the
  * PrintManagerService. It is responsible to connecting to the remote
@@ -439,26 +440,24 @@
     }
 
     /**
-     * Connect to the print spooler service and remove an approved print service.
+     * Remove all approved {@link PrintService print services} that are not in the given set.
      *
-     * @param serviceToRemove The {@link ComponentName} of the service to be removed.
+     * @param servicesToKeep The {@link ComponentName names } of the services to keep
      */
-    public final void removeApprovedPrintService(ComponentName serviceToRemove) {
+    public final void pruneApprovedPrintServices(List<ComponentName> servicesToKeep) {
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
             mCanUnbind = false;
         }
         try {
-            getRemoteInstanceLazy().removeApprovedPrintService(serviceToRemove);
-        } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error removing approved print service.", re);
-        } catch (TimeoutException te) {
-            Slog.e(LOG_TAG, "Error removing approved print service.", te);
+            getRemoteInstanceLazy().pruneApprovedPrintServices(servicesToKeep);
+        } catch (RemoteException|TimeoutException re) {
+            Slog.e(LOG_TAG, "Error pruning approved print services.", re);
         } finally {
             if (DEBUG) {
                 Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
-                        + "] removing approved print service()");
+                        + "] pruneApprovedPrintServices()");
             }
             synchronized (mLock) {
                 mCanUnbind = true;
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 63d3301..dcc02a3 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -21,11 +21,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -98,7 +96,7 @@
     private final List<PrintServiceInfo> mInstalledServices =
             new ArrayList<PrintServiceInfo>();
 
-    private final Set<ComponentName> mEnabledServices =
+    private final Set<ComponentName> mDisabledServices =
             new ArraySet<ComponentName>();
 
     private final PrintJobForAppCache mPrintJobForAppCache =
@@ -126,8 +124,15 @@
         mLock = lock;
         mSpooler = new RemotePrintSpooler(context, userId, this);
         mHandler = new UserStateHandler(context.getMainLooper());
+
         synchronized (mLock) {
-            enableSystemPrintServicesLocked();
+            readInstalledPrintServicesLocked();
+            upgradePersistentStateIfNeeded();
+            readDisabledPrintServicesLocked();
+
+            // Some print services might have gotten installed before the User State came up
+            prunePrintServices();
+
             onConfigurationChangedLocked();
         }
     }
@@ -320,15 +325,6 @@
         }
     }
 
-    /**
-     * Remove an approved print service.
-     *
-     * @param serviceToRemove The {@link ComponentName} of the service to be removed.
-     */
-    public void removeApprovedPrintService(ComponentName serviceToRemove) {
-        mSpooler.removeApprovedPrintService(serviceToRemove);
-    }
-
     public void restartPrintJob(PrintJobId printJobId, int appId) {
         PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, appId);
         if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
@@ -597,13 +593,6 @@
         }
     }
 
-    public Set<ComponentName> getEnabledServices() {
-        synchronized(mLock) {
-            throwIfDestroyedLocked();
-            return mEnabledServices;
-        }
-    }
-
     public void destroyLocked() {
         throwIfDestroyedLocked();
         mSpooler.destroy();
@@ -612,7 +601,7 @@
         }
         mActiveServices.clear();
         mInstalledServices.clear();
-        mEnabledServices.clear();
+        mDisabledServices.clear();
         if (mPrinterDiscoverySession != null) {
             mPrinterDiscoverySession.destroyLocked();
             mPrinterDiscoverySession = null;
@@ -646,12 +635,12 @@
                    .append(installedService.getAdvancedOptionsActivityName()).println();
         }
 
-        pw.append(prefix).append(tab).append("enabled services:").println();
-        for (ComponentName enabledService : mEnabledServices) {
-            String enabledServicePrefix = prefix + tab + tab;
-            pw.append(enabledServicePrefix).append("service:").println();
-            pw.append(enabledServicePrefix).append(tab).append("componentName=")
-                    .append(enabledService.flattenToString());
+        pw.append(prefix).append(tab).append("disabled services:").println();
+        for (ComponentName disabledService : mDisabledServices) {
+            String disabledServicePrefix = prefix + tab + tab;
+            pw.append(disabledServicePrefix).append("service:").println();
+            pw.append(disabledServicePrefix).append(tab).append("componentName=")
+                    .append(disabledService.flattenToString());
             pw.println();
         }
 
@@ -679,7 +668,7 @@
     private boolean readConfigurationLocked() {
         boolean somethingChanged = false;
         somethingChanged |= readInstalledPrintServicesLocked();
-        somethingChanged |= readEnabledPrintServicesLocked();
+        somethingChanged |= readDisabledPrintServicesLocked();
         return somethingChanged;
     }
 
@@ -742,13 +731,50 @@
         return false;
     }
 
-    private boolean readEnabledPrintServicesLocked() {
-        Set<ComponentName> tempEnabledServiceNameSet = new HashSet<ComponentName>();
-        readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES,
-                tempEnabledServiceNameSet);
-        if (!tempEnabledServiceNameSet.equals(mEnabledServices)) {
-            mEnabledServices.clear();
-            mEnabledServices.addAll(tempEnabledServiceNameSet);
+    /**
+     * Update persistent state from a previous version of Android.
+     */
+    private void upgradePersistentStateIfNeeded() {
+        String enabledSettingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ENABLED_PRINT_SERVICES, mUserId);
+
+        // Pre N we store the enabled services, in N and later we store the disabled services.
+        // Hence if enabledSettingValue is still set, we need to upgrade.
+        if (enabledSettingValue != null) {
+            Set<ComponentName> enabledServiceNameSet = new HashSet<ComponentName>();
+            readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES,
+                    enabledServiceNameSet);
+
+            ArraySet<ComponentName> disabledServices = new ArraySet<>();
+            final int numInstalledServices = mInstalledServices.size();
+            for (int i = 0; i < numInstalledServices; i++) {
+                ComponentName serviceName = mInstalledServices.get(i).getComponentName();
+                if (!enabledServiceNameSet.contains(serviceName)) {
+                    disabledServices.add(serviceName);
+                }
+            }
+
+            writeDisabledPrintServicesLocked(disabledServices);
+
+            // We won't needed ENABLED_PRINT_SERVICES anymore, set to null to prevent upgrade to run
+            // again.
+            Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                    Settings.Secure.ENABLED_PRINT_SERVICES, null, mUserId);
+        }
+    }
+
+    /**
+     * Read the set of disabled print services from the secure settings.
+     *
+     * @return true if the state changed.
+     */
+    private boolean readDisabledPrintServicesLocked() {
+        Set<ComponentName> tempDisabledServiceNameSet = new HashSet<ComponentName>();
+        readPrintServicesFromSettingLocked(Settings.Secure.DISABLED_PRINT_SERVICES,
+                tempDisabledServiceNameSet);
+        if (!tempDisabledServiceNameSet.equals(mDisabledServices)) {
+            mDisabledServices.clear();
+            mDisabledServices.addAll(tempDisabledServiceNameSet);
             return true;
         }
         return false;
@@ -774,70 +800,28 @@
         }
     }
 
-    private void enableSystemPrintServicesLocked() {
-        // Load enabled and installed services.
-        readEnabledPrintServicesLocked();
-        readInstalledPrintServicesLocked();
-
-        // Load the system services once enabled on first boot.
-        Set<ComponentName> enabledOnFirstBoot = new HashSet<ComponentName>();
-        readPrintServicesFromSettingLocked(
-                Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES,
-                enabledOnFirstBoot);
-
+    /**
+     * Persist the disabled print services to the secure settings.
+     */
+    private void writeDisabledPrintServicesLocked(Set<ComponentName> disabledServices) {
         StringBuilder builder = new StringBuilder();
-
-        final int serviceCount = mInstalledServices.size();
-        for (int i = 0; i < serviceCount; i++) {
-            ServiceInfo serviceInfo = mInstalledServices.get(i).getResolveInfo().serviceInfo;
-            // Enable system print services if we never did that and are not enabled.
-            if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                ComponentName serviceName = new ComponentName(
-                        serviceInfo.packageName, serviceInfo.name);
-                if (!mEnabledServices.contains(serviceName)
-                        && !enabledOnFirstBoot.contains(serviceName)) {
-                    if (builder.length() > 0) {
-                        builder.append(":");
-                    }
-                    builder.append(serviceName.flattenToString());
-                }
+        for (ComponentName componentName : disabledServices) {
+            if (builder.length() > 0) {
+                builder.append(COMPONENT_NAME_SEPARATOR);
             }
-        }
-
-        // Nothing to be enabled - done.
-        if (builder.length() <= 0) {
-            return;
-        }
-
-        String servicesToEnable = builder.toString();
-
-        // Update the enabled services setting.
-        String enabledServices = Settings.Secure.getStringForUser(
-                mContext.getContentResolver(), Settings.Secure.ENABLED_PRINT_SERVICES, mUserId);
-        if (TextUtils.isEmpty(enabledServices)) {
-            enabledServices = servicesToEnable;
-        } else {
-            enabledServices = enabledServices + ":" + servicesToEnable;
+            builder.append(componentName.flattenToShortString());
         }
         Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                Settings.Secure.ENABLED_PRINT_SERVICES, enabledServices, mUserId);
-
-        // Update the enabled on first boot services setting.
-        String enabledOnFirstBootServices = Settings.Secure.getStringForUser(
-                mContext.getContentResolver(),
-                Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, mUserId);
-        if (TextUtils.isEmpty(enabledOnFirstBootServices)) {
-            enabledOnFirstBootServices = servicesToEnable;
-        } else {
-            enabledOnFirstBootServices = enabledOnFirstBootServices + ":" + enabledServices;
-        }
-        Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES,
-                enabledOnFirstBootServices, mUserId);
+                Settings.Secure.DISABLED_PRINT_SERVICES, builder.toString(), mUserId);
     }
 
-    private void onConfigurationChangedLocked() {
-        Set<ComponentName> installedComponents = new ArraySet<ComponentName>();
+    /**
+     * Get the {@link ComponentName names} of the installed print services
+     *
+     * @return The names of the installed print services
+     */
+    private ArrayList<ComponentName> getInstalledComponents() {
+        ArrayList<ComponentName> installedComponents = new ArrayList<ComponentName>();
 
         final int installedCount = mInstalledServices.size();
         for (int i = 0; i < installedCount; i++) {
@@ -846,8 +830,37 @@
                     resolveInfo.serviceInfo.name);
 
             installedComponents.add(serviceName);
+        }
 
-            if (mEnabledServices.contains(serviceName)) {
+        return installedComponents;
+    }
+
+    /**
+     * Prune persistent state if a print service was uninstalled
+     */
+    public void prunePrintServices() {
+        synchronized (mLock) {
+            ArrayList<ComponentName> installedComponents = getInstalledComponents();
+
+            // Remove unnecessary entries from persistent state "disabled services"
+            boolean disabledServicesUninstalled = mDisabledServices.retainAll(installedComponents);
+            if (disabledServicesUninstalled) {
+                writeDisabledPrintServicesLocked(mDisabledServices);
+            }
+
+            // Remove unnecessary entries from persistent state "approved services"
+            mSpooler.pruneApprovedPrintServices(installedComponents);
+        }
+    }
+
+    private void onConfigurationChangedLocked() {
+        ArrayList<ComponentName> installedComponents = getInstalledComponents();
+
+        final int installedCount = installedComponents.size();
+        for (int i = 0; i < installedCount; i++) {
+            ComponentName serviceName = installedComponents.get(i);
+
+            if (!mDisabledServices.contains(serviceName)) {
                 if (!mActiveServices.containsKey(serviceName)) {
                     RemotePrintService service = new RemotePrintService(
                             mContext, serviceName, mUserId, mSpooler, this);
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index e4738f5..5ad3379 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -1095,13 +1095,6 @@
     analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette,
                   &paletteEntries, &hasTransparency, &color_type, outRows);
 
-    // If the image is a 9-patch, we need to preserve it as a ARGB file to make
-    // sure the pixels will not be pre-dithered/clamped until we decide they are
-    if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB ||
-            color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
-        color_type = PNG_COLOR_TYPE_RGB_ALPHA;
-    }
-
     if (kIsDebug) {
         switch (color_type) {
         case PNG_COLOR_TYPE_PALETTE:
@@ -1180,18 +1173,11 @@
         }
 
         for (int i = 0; i < chunk_count; i++) {
-            unknowns[i].location = PNG_HAVE_PLTE;
+            unknowns[i].location = PNG_HAVE_IHDR;
         }
         png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
                                     chunk_names, chunk_count);
         png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count);
-#if PNG_LIBPNG_VER < 10600
-        /* Deal with unknown chunk location bug in 1.5.x and earlier */
-        png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
-        if (imageInfo.haveLayoutBounds) {
-            png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE);
-        }
-#endif
     }