Merge "Fix inefficient CursorAnchorInfo#hashCode()." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 3444597..fbbb2e3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3578,6 +3578,7 @@
     method public final deprecated void removeDialog(int);
     method public void reportFullyDrawn();
     method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
+    method public final void requestKeyboardShortcutsHelper();
     method public final void requestPermissions(java.lang.String[], int);
     method public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index a4927f2..d91f5d1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3696,6 +3696,7 @@
     method public final deprecated void removeDialog(int);
     method public void reportFullyDrawn();
     method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
+    method public final void requestKeyboardShortcutsHelper();
     method public final void requestPermissions(java.lang.String[], int);
     method public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 4179948..bad9733 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3578,6 +3578,7 @@
     method public final deprecated void removeDialog(int);
     method public void reportFullyDrawn();
     method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
+    method public final void requestKeyboardShortcutsHelper();
     method public final void requestPermissions(java.lang.String[], int);
     method public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 672a706..7652766 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1676,6 +1676,17 @@
     public void onProvideAssistContent(AssistContent outContent) {
     }
 
+    /**
+     * Request the Keyboard Shortcuts screen to show up. If it succeeds, this will trigger
+     * {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
+     */
+    public final void requestKeyboardShortcutsHelper() {
+        Intent intent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
+        intent.setComponent(new ComponentName("com.android.systemui",
+                "com.android.systemui.statusbar.KeyboardShortcutsReceiver"));
+        sendBroadcast(intent);
+    }
+
     @Override
     public void onProvideKeyboardShortcuts(
             List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 207b70a..7e67e8d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1404,6 +1404,16 @@
     public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
 
     /**
+     * Activity Action: Start the Keyboard Shortcuts Helper screen.
+     * <p>Input: Nothing.
+     * <p>Output: Nothing.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SHOW_KEYBOARD_SHORTCUTS =
+            "android.intent.action.SHOW_KEYBOARD_SHORTCUTS";
+
+    /**
      * Activity Action: Show settings for managing network data usage of a
      * specific application. Applications should define an activity that offers
      * options to control data usage.
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index e85311d..8236f55 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -36,22 +36,21 @@
  * @hide
  */
 public class PackageUserState {
+    public long ceDataInode;
+    public boolean installed;
     public boolean stopped;
     public boolean notLaunched;
-    public boolean installed;
     public boolean hidden; // Is the app restricted by owner / admin
     public boolean suspended;
-    public int enabled;
     public boolean blockUninstall;
-
+    public int enabled;
     public String lastDisableAppCaller;
+    public int domainVerificationStatus;
+    public int appLinkGeneration;
 
     public ArraySet<String> disabledComponents;
     public ArraySet<String> enabledComponents;
 
-    public int domainVerificationStatus;
-    public int appLinkGeneration;
-
     public PackageUserState() {
         installed = true;
         hidden = false;
@@ -62,18 +61,19 @@
     }
 
     public PackageUserState(PackageUserState o) {
+        ceDataInode = o.ceDataInode;
         installed = o.installed;
         stopped = o.stopped;
         notLaunched = o.notLaunched;
-        enabled = o.enabled;
         hidden = o.hidden;
         suspended = o.suspended;
-        lastDisableAppCaller = o.lastDisableAppCaller;
-        disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
-        enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
         blockUninstall = o.blockUninstall;
+        enabled = o.enabled;
+        lastDisableAppCaller = o.lastDisableAppCaller;
         domainVerificationStatus = o.domainVerificationStatus;
         appLinkGeneration = o.appLinkGeneration;
+        disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
+        enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
     }
 
     /**
diff --git a/core/java/com/android/internal/app/procstats/SparseMappingTable.java b/core/java/com/android/internal/app/procstats/SparseMappingTable.java
index cd4f7b6..76102af 100644
--- a/core/java/com/android/internal/app/procstats/SparseMappingTable.java
+++ b/core/java/com/android/internal/app/procstats/SparseMappingTable.java
@@ -629,7 +629,7 @@
      * this is an eng build.)
      */
     private static void logOrThrow(String message, Throwable th) {
-        Slog.wtf(TAG, message, th);
+        Slog.e(TAG, message, th);
         if (Build.TYPE.equals("eng")) {
             throw new RuntimeException(message, th);
         }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
index 85cc841..46b49de 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -285,7 +285,7 @@
         }
 
         public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
-                InputMethodInfo imi, InputMethodSubtype subtype) {
+                InputMethodInfo imi, InputMethodSubtype subtype, boolean forward) {
             if (imi == null) {
                 return null;
             }
@@ -297,8 +297,9 @@
                 return null;
             }
             final int N = mImeSubtypeList.size();
-            for (int offset = 1; offset < N; ++offset) {
+            for (int i = 1; i < N; ++i) {
                 // Start searching the next IME/subtype from the next of the current index.
+                final int offset = forward ? i : N - i;
                 final int candidateIndex = (currentIndex + offset) % N;
                 final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex);
                 // Skip if searching inside the current IME only, but the candidate is not
@@ -371,7 +372,7 @@
         }
 
         public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
-                InputMethodInfo imi, InputMethodSubtype subtype) {
+                InputMethodInfo imi, InputMethodSubtype subtype, boolean forward) {
             int currentUsageRank = getUsageRank(imi, subtype);
             if (currentUsageRank < 0) {
                 if (DEBUG) {
@@ -381,7 +382,8 @@
             }
             final int N = mUsageHistoryOfSubtypeListItemIndex.length;
             for (int i = 1; i < N; i++) {
-                final int subtypeListItemRank = (currentUsageRank + i) % N;
+                final int offset = forward ? i : N - i;
+                final int subtypeListItemRank = (currentUsageRank + offset) % N;
                 final int subtypeListItemIndex =
                         mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank];
                 final ImeSubtypeListItem subtypeListItem =
@@ -455,16 +457,16 @@
         }
 
         public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi,
-                InputMethodSubtype subtype) {
+                InputMethodSubtype subtype, boolean forward) {
             if (imi == null) {
                 return null;
             }
             if (imi.supportsSwitchingToNextInputMethod()) {
                 return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
-                        subtype);
+                        subtype, forward);
             } else {
                 return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
-                        subtype);
+                        subtype, forward);
             }
         }
 
@@ -532,14 +534,14 @@
     }
 
     public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi,
-            InputMethodSubtype subtype) {
+            InputMethodSubtype subtype, boolean forward) {
         if (mController == null) {
             if (DEBUG) {
                 Log.e(TAG, "mController shouldn't be null.");
             }
             return null;
         }
-        return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
+        return mController.getNextInputMethod(onlyCurrentIme, imi, subtype, forward);
     }
 
     public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index 2a9264d..47f2c70 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -103,21 +103,7 @@
         }
     }
 
-    public void execute(String cmd, Object... args) throws InstallerException {
-        final String resRaw = executeForResult(cmd, args);
-        int res = -1;
-        try {
-            res = Integer.parseInt(resRaw);
-        } catch (NumberFormatException ignored) {
-        }
-        if (res != 0) {
-            throw new InstallerException(
-                    "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res);
-        }
-    }
-
-    public String executeForResult(String cmd, Object... args)
-            throws InstallerException {
+    public String[] execute(String cmd, Object... args) throws InstallerException {
         final StringBuilder builder = new StringBuilder(cmd);
         for (Object arg : args) {
             String escaped;
@@ -135,7 +121,17 @@
             }
             builder.append(' ').append(escaped);
         }
-        return transact(builder.toString());
+        final String[] resRaw = transact(builder.toString()).split(" ");
+        int res = -1;
+        try {
+            res = Integer.parseInt(resRaw[0]);
+        } catch (ArrayIndexOutOfBoundsException | NumberFormatException ignored) {
+        }
+        if (res != 0) {
+            throw new InstallerException(
+                    "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res);
+        }
+        return resRaw;
     }
 
     public void dexopt(String apkPath, int uid, String instructionSet, int dexoptNeeded,
@@ -160,19 +156,15 @@
     }
 
     public boolean mergeProfiles(int uid, String pkgName) throws InstallerException {
-        String rawReply = executeForResult("merge_profiles", uid, pkgName);
-        if (rawReply == null) {
-            throw new IllegalStateException("Unexpected null reply");
-        }
-        final String res[] = rawReply.split(" ");
+        final String[] res = execute("merge_profiles", uid, pkgName);
 
         if ((res == null) || (res.length != 2)) {
-            throw new InstallerException("Invalid size result: " + rawReply);
+            throw new InstallerException("Invalid size result: " + Arrays.toString(res));
         }
 
         // Just as a sanity check. Anything != "true" will be interpreted as false by parseBoolean.
         if (!res[1].equals("true") && !res[1].equals("false")) {
-            throw new InstallerException("Invalid boolean result: " + rawReply);
+            throw new InstallerException("Invalid boolean result: " + Arrays.toString(res));
         }
 
         return Boolean.parseBoolean(res[1]);
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index bed5a2e..18f715e 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -27,6 +27,7 @@
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -128,7 +129,7 @@
     /**
      * Checks if given array is null or has zero elements.
      */
-    public static boolean isEmpty(@Nullable List<?> array) {
+    public static boolean isEmpty(@Nullable Collection<?> array) {
         return array == null || array.isEmpty();
     }
 
@@ -451,7 +452,7 @@
         }
     }
 
-    public static <T> boolean contains(@Nullable ArrayList<T> cur, T val) {
+    public static <T> boolean contains(@Nullable Collection<T> cur, T val) {
         return (cur != null) ? cur.contains(val) : false;
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index ec5220f..ba5206a 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -27,7 +27,6 @@
 
 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
-import com.android.internal.inputmethod.InputMethodUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -68,7 +67,7 @@
         ri.serviceInfo = si;
         List<InputMethodSubtype> subtypes = null;
         if (subtypeLocales != null) {
-            subtypes = new ArrayList<InputMethodSubtype>();
+            subtypes = new ArrayList<>();
             for (String subtypeLocale : subtypeLocales) {
                 subtypes.add(createDummySubtype(subtypeLocale));
             }
@@ -89,7 +88,7 @@
     }
 
     private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
-        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+        final List<ImeSubtypeListItem> items = new ArrayList<>();
         addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
                 true /* supportsSwitchingToNextInputMethod*/);
         addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme",
@@ -105,7 +104,7 @@
     }
 
     private static List<ImeSubtypeListItem> createDisabledImeSubtypes() {
-        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+        final List<ImeSubtypeListItem> items = new ArrayList<>();
         addDummyImeSubtypeListItems(items,
                 "UnknownIme", "UnknownIme",
                 Arrays.asList("en_US", "hi"),
@@ -121,15 +120,18 @@
     }
 
     private void assertNextInputMethod(final ControllerImpl controller,
-            final boolean onlyCurrentIme,
-            final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) {
+            final boolean onlyCurrentIme, final ImeSubtypeListItem currentItem,
+            final ImeSubtypeListItem nextItem, final ImeSubtypeListItem prevItem) {
         InputMethodSubtype subtype = null;
         if (currentItem.mSubtypeName != null) {
             subtype = createDummySubtype(currentItem.mSubtypeName.toString());
         }
         final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
-                currentItem.mImi, subtype);
+                currentItem.mImi, subtype, true /* forward */);
         assertEquals(nextItem, nextIme);
+        final ImeSubtypeListItem prevIme = controller.getNextInputMethod(onlyCurrentIme,
+                currentItem.mImi, subtype, false /* forward */);
+        assertEquals(prevItem, prevIme);
     }
 
     private void assertRotationOrder(final ControllerImpl controller,
@@ -138,11 +140,13 @@
         final int N = expectedRotationOrderOfImeSubtypeList.length;
         for (int i = 0; i < N; i++) {
             final int currentIndex = i;
+            final int prevIndex = (currentIndex + N - 1) % N;
             final int nextIndex = (currentIndex + 1) % N;
             final ImeSubtypeListItem currentItem =
                     expectedRotationOrderOfImeSubtypeList[currentIndex];
             final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex];
-            assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem);
+            final ImeSubtypeListItem prevItem = expectedRotationOrderOfImeSubtypeList[prevIndex];
+            assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem, prevItem);
         }
     }
 
@@ -190,29 +194,29 @@
         assertRotationOrder(controller, true /* onlyCurrentIme */,
                 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                subtypeUnawareIme, null);
+                subtypeUnawareIme, null, null);
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                japaneseIme_ja_JP, null);
+                japaneseIme_ja_JP, null, null);
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                switchUnawareJapaneseIme_ja_JP, null);
+                switchUnawareJapaneseIme_ja_JP, null, null);
 
         // Make sure that disabled IMEs are not accepted.
         assertNextInputMethod(controller, false /* onlyCurrentIme */,
-                disabledIme_en_US, null);
+                disabledIme_en_US, null, null);
         assertNextInputMethod(controller, false /* onlyCurrentIme */,
-                disabledIme_hi, null);
+                disabledIme_hi, null, null);
         assertNextInputMethod(controller, false /* onlyCurrentIme */,
-                disabledSwitchingUnawareIme, null);
+                disabledSwitchingUnawareIme, null, null);
         assertNextInputMethod(controller, false /* onlyCurrentIme */,
-                disabledSubtypeUnawareIme, null);
+                disabledSubtypeUnawareIme, null, null);
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                disabledIme_en_US, null);
+                disabledIme_en_US, null, null);
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                disabledIme_hi, null);
+                disabledIme_hi, null, null);
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                disabledSwitchingUnawareIme, null);
+                disabledSwitchingUnawareIme, null, null);
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                disabledSubtypeUnawareIme, null);
+                disabledSubtypeUnawareIme, null, null);
     }
 
     @SmallTest
@@ -246,7 +250,7 @@
                 japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
         // Check onlyCurrentIme == true.
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                japaneseIme_ja_JP, null);
+                japaneseIme_ja_JP, null, null);
         assertRotationOrder(controller, true /* onlyCurrentIme */,
                 latinIme_fr, latinIme_en_US);
         assertRotationOrder(controller, true /* onlyCurrentIme */,
@@ -270,9 +274,9 @@
         assertRotationOrder(controller, true /* onlyCurrentIme */,
                 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                subtypeUnawareIme, null);
+                subtypeUnawareIme, null, null);
         assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                switchUnawareJapaneseIme_ja_JP, null);
+                switchUnawareJapaneseIme_ja_JP, null, null);
 
         // Rotation order should be preserved when created with the same subtype list.
         final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
@@ -298,7 +302,7 @@
 
     @SmallTest
     public void testImeSubtypeListItem() throws Exception {
-        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+        final List<ImeSubtypeListItem> items = new ArrayList<>();
         addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme",
                 Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
                 true /* supportsSwitchingToNextInputMethod*/);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index b34af0b..84fc6fe 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -98,7 +98,7 @@
             assert(uri == null || uri.getAuthority() == null ||
                     LauncherActivity.isLaunchUri(uri));
             refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
-        } else if (intent.getAction() == Intent.ACTION_VIEW) {
+        } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
             assert(uri != null);
             new OpenUriForViewTask(this).executeOnExecutor(
                     ProviderExecutor.forAuthority(uri.getAuthority()), uri);
@@ -276,18 +276,6 @@
 
     @Override
     public void onDocumentPicked(DocumentInfo doc, Model model) {
-        if (doc.isContainer()) {
-            openContainerDocument(doc);
-        } else {
-            openDocument(doc, model);
-        }
-    }
-
-    /**
-     * Launches an intent to view the specified document.
-     */
-    private void openDocument(DocumentInfo doc, Model model) {
-
         // Anything on downloads goes through the back through downloads manager
         // (that's the MANAGE_DOCUMENT bit).
         // This is done for two reasons:
@@ -297,7 +285,13 @@
         //    like origin URL.
         // All other files not on downloads, event APKs, would get no benefit from this
         // treatment, thusly the "isDownloads" check.
-        if (getCurrentRoot().isDownloads()) {
+
+        // Launch MANAGE_DOCUMENTS only for the root level files, so it's not called for
+        // files in archives. Also, if the activity is already browsing a ZIP from downloads,
+        // then skip MANAGE_DOCUMENTS.
+        final boolean isViewing = Intent.ACTION_VIEW.equals(getIntent().getAction());
+        final boolean isInArchive = mState.stack.size() > 1;
+        if (getCurrentRoot().isDownloads() && !isInArchive && !isViewing) {
             // First try managing the document; we expect manager to filter
             // based on authority, so we don't grant.
             final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
@@ -311,6 +305,17 @@
             }
         }
 
+        if (doc.isContainer()) {
+            openContainerDocument(doc);
+        } else {
+            openDocument(doc, model);
+        }
+    }
+
+    /**
+     * Launches an intent to view the specified document.
+     */
+    private void openDocument(DocumentInfo doc, Model model) {
         Intent intent = new QuickViewIntentBuilder(
                 getPackageManager(), getResources(), doc, model).build();
 
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 246b95de..329afdd 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -33,7 +33,6 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Date;
 import java.util.LinkedList;
 
@@ -118,9 +117,10 @@
     synchronized @Nullable LoaderTask getNextTaskOrReleaseBackgroundThread() {
         Preconditions.checkState(mBackgroundThread != null);
 
-        final LoaderTask task = mTaskList.findRunningTask();
-        if (task != null) {
-            return task;
+        for (final LoaderTask task : mTaskList) {
+            if (task.getState() == LoaderTask.STATE_LOADING) {
+                return task;
+            }
         }
 
         final Identifier identifier = mDatabase.getUnmappedDocumentsParent(mDevice.deviceId);
@@ -161,8 +161,21 @@
         mTaskList.clearCompletedTasks();
     }
 
-    synchronized void clearTask(Identifier parentIdentifier) {
-        mTaskList.clearTask(parentIdentifier);
+    /**
+     * Cancels the task for |parentIdentifier|.
+     *
+     * Task is removed from the cached list and it will create new task when |parentIdentifier|'s
+     * children are queried next.
+     */
+    void cancelTask(Identifier parentIdentifier) {
+        final LoaderTask task;
+        synchronized (this) {
+            task = mTaskList.findTask(parentIdentifier);
+        }
+        if (task != null) {
+            task.cancel();
+            mTaskList.remove(task);
+        }
     }
 
     /**
@@ -205,14 +218,6 @@
             return null;
         }
 
-        LoaderTask findRunningTask() {
-            for (int i = 0; i < size(); i++) {
-                if (get(i).getState() == LoaderTask.STATE_LOADING)
-                    return get(i);
-            }
-            return null;
-        }
-
         void clearCompletedTasks() {
             int i = 0;
             while (i < size()) {
@@ -223,17 +228,6 @@
                 }
             }
         }
-
-        void clearTask(Identifier parentIdentifier) {
-            for (int i = 0; i < size(); i++) {
-                final LoaderTask task = get(i);
-                if (task.mIdentifier.mDeviceId == parentIdentifier.mDeviceId &&
-                        task.mIdentifier.mObjectHandle == parentIdentifier.mObjectHandle) {
-                    remove(i);
-                    return;
-                }
-            }
-        }
     }
 
     /**
@@ -245,6 +239,7 @@
         static final int STATE_LOADING = 1;
         static final int STATE_COMPLETED = 2;
         static final int STATE_ERROR = 3;
+        static final int STATE_CANCELLED = 4;
 
         final MtpManager mManager;
         final MtpDatabase mDatabase;
@@ -272,6 +267,7 @@
 
         synchronized void loadObjectHandles() {
             assert mState == STATE_START;
+            mPosition = 0;
             int parentHandle = mIdentifier.mObjectHandle;
             // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
             // getObjectHandles if we would like to obtain children under the root.
@@ -303,12 +299,10 @@
                 case STATE_ERROR:
                     throw mError;
             }
-
             final Cursor cursor =
                     mDatabase.queryChildDocuments(columnNames, mIdentifier.mDocumentId);
+            cursor.setExtras(extras);
             cursor.setNotificationUri(resolver, createUri());
-            cursor.respond(extras);
-
             return cursor;
         }
 
@@ -374,6 +368,10 @@
                 }
             }
             synchronized (this) {
+                // Check if the task is cancelled or not.
+                if (mState != STATE_LOADING) {
+                    return;
+                }
                 try {
                     mDatabase.getMapper().putChildDocuments(
                             mIdentifier.mDeviceId,
@@ -403,6 +401,14 @@
         }
 
         /**
+         * Cancels the task.
+         */
+        synchronized void cancel() {
+            mDatabase.getMapper().cancelAddingDocuments(mIdentifier.mDocumentId);
+            mState = STATE_CANCELLED;
+        }
+
+        /**
          * Returns a state of the task.
          */
         int getState() {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index adc71ae..63f18f3 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -363,6 +363,41 @@
     }
 
     /**
+     * Cancels adding documents.
+     * @param parentId
+     */
+    void cancelAddingDocuments(@Nullable String parentId) {
+        final String selection;
+        final String[] args;
+        if (parentId != null) {
+            selection = COLUMN_PARENT_DOCUMENT_ID + " = ?";
+            args = strings(parentId);
+        } else {
+            selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+            args = EMPTY_ARGS;
+        }
+
+        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+        database.beginTransaction();
+        try {
+            if (!mInMappingIds.contains(parentId)) {
+                return;
+            }
+            mInMappingIds.remove(parentId);
+            final ContentValues values = new ContentValues();
+            values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+            mDatabase.getSQLiteDatabase().update(
+                    TABLE_DOCUMENTS,
+                    values,
+                    selection + " AND " + COLUMN_ROW_STATE + " = ?",
+                    DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_INVALIDATED)));
+            database.setTransactionSuccessful();
+        } finally {
+            database.endTransaction();
+        }
+    }
+
+    /**
      * Queries candidate for each mappingKey, and returns the first cursor that includes a
      * candidate.
      *
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 50781bf..1823711 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -308,7 +308,7 @@
             final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId);
             mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
             mDatabase.deleteDocument(documentId);
-            getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
+            getDocumentLoader(parentIdentifier).cancelTask(parentIdentifier);
             notifyChildDocumentsChange(parentIdentifier.mDocumentId);
             if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
                 // If the parent is storage, the object might be appeared as child of device because
@@ -402,7 +402,7 @@
             final String documentId = mDatabase.putNewDocument(
                     parentId.mDeviceId, parentDocumentId, record.operationsSupported,
                     infoWithHandle, 0l);
-            getDocumentLoader(parentId).clearTask(parentId);
+            getDocumentLoader(parentId).cancelTask(parentId);
             notifyChildDocumentsChange(parentDocumentId);
             return documentId;
         } catch (FileNotFoundException | RuntimeException error) {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index 45f89e4..60dd7e1 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -21,6 +21,7 @@
 import android.mtp.MtpObjectInfo;
 import android.net.Uri;
 import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
@@ -28,6 +29,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeoutException;
 
 @MediumTest
 public class DocumentLoaderTest extends AndroidTestCase {
@@ -141,6 +143,33 @@
         }
     }
 
+    public void testCancelTask() throws IOException, InterruptedException {
+        setUpDocument(mManager,
+                DocumentLoader.NUM_INITIAL_ENTRIES + DocumentLoader.NUM_LOADING_ENTRIES + 1);
+
+        // Block the first iteration in the background thread.
+        mManager.blockDocument(
+                0, DocumentLoader.NUM_INITIAL_ENTRIES + 1);
+        setUpLoader();
+        try (final Cursor cursor = mLoader.queryChildDocuments(
+                MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {
+            assertTrue(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
+        }
+        Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS);
+
+        // Clear task while the first iteration is being blocked.
+        mManager.unblockDocument(
+                0, DocumentLoader.NUM_INITIAL_ENTRIES + 1);
+        mLoader.cancelTask(mParentIdentifier);
+
+        Thread.sleep(DocumentLoader.NOTIFY_PERIOD_MS * 2);
+
+        // Check if it's OK to query invalidated task.
+        try (final Cursor cursor = mLoader.queryChildDocuments(
+                MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {
+        }
+    }
+
     private void setUpLoader() {
         mLoader = new DocumentLoader(
                 new MtpDeviceRecord(
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9e2442c..f5854f5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -488,5 +488,12 @@
                 <action android:name="com.android.systemui.action.CLEAR_TUNER" />
             </intent-filter>
         </receiver>
+
+        <receiver
+            android:name=".statusbar.KeyboardShortcutsReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.SHOW_KEYBOARD_SHORTCUTS" />
+            </intent-filter>
+        </receiver>
     </application>
 </manifest>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
index 3067714..2045ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.util.AttributeSet;
 import android.view.View;
 
@@ -51,6 +52,12 @@
                 || touchY > mContent.getY() + mContent.getHeight();
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mDismissButton.setText(R.string.clear_all_notifications_text);
+    }
+
     public boolean isButtonVisible() {
         return mDismissButton.getAlpha() != 0.0f;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index fff1491..11aaedf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -255,10 +255,7 @@
     }
 
     public void toggleKeyboardShortcuts(int deviceId) {
-        InputDevice inputDevice = InputManager.getInstance().getInputDevice(deviceId);
-        if (inputDevice != null) {
-            mKeyCharacterMap = inputDevice.getKeyCharacterMap();
-        }
+        retrieveKeyCharacterMap(deviceId);
         if (mKeyboardShortcutsDialog == null) {
             Recents.getSystemServices().requestKeyboardShortcuts(mContext,
                 new KeyboardShortcutsReceiver() {
@@ -278,6 +275,35 @@
         }
     }
 
+    /**
+     * Retrieves a {@link KeyCharacterMap} and assigns it to mKeyCharacterMap. If the given id is an
+     * existing device, that device's map is used. Otherwise, it checks first all available devices
+     * and if there is a full keyboard it uses that map, otherwise falls back to the Virtual
+     * Keyboard with its default map.
+     */
+    private void retrieveKeyCharacterMap(int deviceId) {
+        final InputManager inputManager = InputManager.getInstance();
+        if (deviceId != -1) {
+            final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+            if (inputDevice != null) {
+                mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+                return;
+            }
+        }
+        final int[] deviceIds = inputManager.getInputDeviceIds();
+        for (int i = 0; i < deviceIds.length; ++i) {
+            final InputDevice inputDevice = inputManager.getInputDevice(deviceIds[i]);
+            // -1 is the Virtual Keyboard, with the default key map. Use that one only as last
+            // resort.
+            if (inputDevice.getId() != -1 && inputDevice.isFullKeyboard()) {
+                mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+                return;
+            }
+        }
+        final InputDevice inputDevice = inputManager.getInputDevice(-1);
+        mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+    }
+
     public void dismissKeyboardShortcutsDialog() {
         if (mKeyboardShortcutsDialog != null) {
             mKeyboardShortcutsDialog.dismiss();
@@ -488,12 +514,6 @@
             final int itemsSize = group.getItems().size();
             for (int j = 0; j < itemsSize; j++) {
                 KeyboardShortcutInfo info = group.getItems().get(j);
-                if (info.getKeycode() != KeyEvent.KEYCODE_UNKNOWN
-                        && !KeyCharacterMap.deviceHasKey(info.getKeycode())) {
-                    // The user can't achieve this shortcut, so skipping.
-                    Log.w(TAG, "Keyboard Shortcut contains key not on device, skipping.");
-                    continue;
-                }
                 List<StringOrDrawable> shortcutKeys = getHumanReadableShortcutKeys(info);
                 if (shortcutKeys == null) {
                     // Ignore shortcuts we can't display keys for.
@@ -585,9 +605,7 @@
             if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
                 return shortcutKeys;
             }
-            // TODO: Have a generic map for when we don't have the device's.
-            char displayLabel = mKeyCharacterMap == null
-                    ? 0 : mKeyCharacterMap.getDisplayLabel(info.getKeycode());
+            char displayLabel = mKeyCharacterMap.getDisplayLabel(info.getKeycode());
             if (displayLabel != 0) {
                 displayLabelString = String.valueOf(displayLabel);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
new file mode 100644
index 0000000..5d22faf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
@@ -0,0 +1,33 @@
+/*
+ * 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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Receiver for the Keyboard Shortcuts Helper.
+ */
+public class KeyboardShortcutsReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
+            final KeyboardShortcuts keyboardShortcuts = new KeyboardShortcuts(context);
+            keyboardShortcuts.toggleKeyboardShortcuts(-1 /* deviceId unknown */);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
new file mode 100644
index 0000000..03b51c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
@@ -0,0 +1,271 @@
+/*
+ * 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.car;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * A {@link BatteryController} that is specific to the Auto use-case. For Auto, the battery icon
+ * displays the battery status of a device that is connected via bluetooth and not the system's
+ * battery.
+ */
+public class CarBatteryController extends BroadcastReceiver implements BatteryController {
+    private static final String TAG = "CarBatteryController";
+
+    // According to the Bluetooth HFP 1.5 specification, battery levels are indicated by a
+    // value from 1-5, where these values represent the following:
+    // 0%% - 0, 1-25%% - 1, 26-50%% - 2, 51-75%% - 3, 76-99%% - 4, 100%% - 5
+    // As a result, set the level as the average within that range.
+    private static final int BATTERY_LEVEL_EMPTY = 0;
+    private static final int BATTERY_LEVEL_1 = 12;
+    private static final int BATTERY_LEVEL_2 = 28;
+    private static final int BATTERY_LEVEL_3 = 63;
+    private static final int BATTERY_LEVEL_4 = 87;
+    private static final int BATTERY_LEVEL_FULL = 100;
+
+    private static final int INVALID_BATTERY_LEVEL = -1;
+
+    private final Context mContext;
+
+    private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+    private BluetoothHeadsetClient mBluetoothHeadsetClient;
+
+    private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+
+    private int mLevel;
+
+    /**
+     * An interface indicating the container of a View that will display what the information
+     * in the {@link CarBatteryController}.
+     */
+    public interface BatteryViewHandler {
+        void hideBatteryView();
+        void showBatteryView();
+    }
+
+    private BatteryViewHandler mBatteryViewHandler;
+
+    public CarBatteryController(Context context) {
+        mContext = context;
+
+        mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener,
+                BluetoothProfile.HEADSET_CLIENT);
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("CarBatteryController state:");
+        pw.print("    mLevel=");
+        pw.println(mLevel);
+    }
+
+    @Override
+    public void setPowerSaveMode(boolean powerSave) {
+        // No-op. No power save mode for the car.
+    }
+
+    @Override
+    public void addStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+        mChangeCallbacks.add(cb);
+
+        // There is no way to know if the phone is plugged in or charging via bluetooth, so pass
+        // false for these values.
+        cb.onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */);
+        cb.onPowerSaveChanged(false /* isPowerSave */);
+    }
+
+    @Override
+    public void removeStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+        mChangeCallbacks.remove(cb);
+    }
+
+    public void addBatteryViewHandler(BatteryViewHandler batteryViewHandler) {
+        mBatteryViewHandler = batteryViewHandler;
+    }
+
+    public void startListening() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT);
+        mContext.registerReceiver(this, filter);
+    }
+
+    public void stopListening() {
+        mContext.unregisterReceiver(this);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onReceive(). action: " + action);
+        }
+
+        if (BluetoothHeadsetClient.ACTION_AG_EVENT.equals(action)) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Received ACTION_AG_EVENT");
+            }
+
+            int batteryLevel = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
+                    INVALID_BATTERY_LEVEL);
+
+            updateBatteryLevel(batteryLevel);
+
+            if (batteryLevel != INVALID_BATTERY_LEVEL && mBatteryViewHandler != null) {
+                mBatteryViewHandler.showBatteryView();
+            }
+        } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+            int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+                Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: "
+                        + oldState + " -> " + newState);
+
+            }
+            BluetoothDevice device =
+                    (BluetoothDevice)intent.getExtra(BluetoothDevice.EXTRA_DEVICE);
+            updateBatteryIcon(device, newState);
+        }
+    }
+
+    /**
+     * Converts the battery level to a percentage that can be displayed on-screen and notifies
+     * any {@link BatteryStateChangeCallback}s of this.
+     */
+    private void updateBatteryLevel(int batteryLevel) {
+        if (batteryLevel == INVALID_BATTERY_LEVEL) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Battery level invalid. Ignoring.");
+            }
+            return;
+        }
+
+        // The battery level is a value between 0-5. Let the default battery level be 0.
+        switch (batteryLevel) {
+            case 5:
+                mLevel = BATTERY_LEVEL_FULL;
+                break;
+            case 4:
+                mLevel = BATTERY_LEVEL_4;
+                break;
+            case 3:
+                mLevel = BATTERY_LEVEL_3;
+                break;
+            case 2:
+                mLevel = BATTERY_LEVEL_2;
+                break;
+            case 1:
+                mLevel = BATTERY_LEVEL_1;
+                break;
+            case 0:
+            default:
+                mLevel = BATTERY_LEVEL_EMPTY;
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Battery level: " + batteryLevel + "; setting mLevel as: " + mLevel);
+        }
+
+        notifyBatteryLevelChanged();
+    }
+
+    /**
+     * Updates the display of the battery icon depending on the given connection state from the
+     * given {@link BluetoothDevice}.
+     */
+    private void updateBatteryIcon(BluetoothDevice device, int newState) {
+        if (newState == BluetoothProfile.STATE_CONNECTED) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Device connected");
+            }
+
+            if (mBatteryViewHandler != null) {
+                mBatteryViewHandler.showBatteryView();
+            }
+
+            if (mBluetoothHeadsetClient == null || device == null) {
+                return;
+            }
+
+            // Check if battery information is available and immediately update.
+            Bundle featuresBundle = mBluetoothHeadsetClient.getCurrentAgEvents(device);
+            if (featuresBundle == null) {
+                return;
+            }
+
+            int batteryLevel = featuresBundle.getInt(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
+                    INVALID_BATTERY_LEVEL);
+            updateBatteryLevel(batteryLevel);
+        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Device disconnected");
+            }
+
+            if (mBatteryViewHandler != null) {
+                mBatteryViewHandler.hideBatteryView();
+            }
+        }
+    }
+
+    @Override
+    public boolean isPowerSave() {
+        // Power save is not valid for the car, so always return false.
+        return false;
+    }
+
+    private void notifyBatteryLevelChanged() {
+        for (int i = 0, size = mChangeCallbacks.size(); i < size; i++) {
+            mChangeCallbacks.get(i)
+                    .onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */);
+        }
+    }
+
+    private final ServiceListener mHfpServiceListener = new ServiceListener() {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (profile == BluetoothProfile.HEADSET_CLIENT) {
+                mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (profile == BluetoothProfile.HEADSET_CLIENT) {
+                mBluetoothHeadsetClient = null;
+            }
+        }
+    };
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 4add3cb..811687c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -22,37 +22,75 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.PixelFormat;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewStub;
 import android.view.WindowManager;
-
+import com.android.systemui.BatteryMeterView;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.PhoneStatusBarView;
+import com.android.systemui.statusbar.policy.BatteryController;
 
 /**
  * A status bar (and navigation bar) tailored for the automotive use case.
  */
-public class CarStatusBar extends PhoneStatusBar {
+public class CarStatusBar extends PhoneStatusBar implements
+        CarBatteryController.BatteryViewHandler {
+    private static final String TAG = "CarStatusBar";
+
     private TaskStackListenerImpl mTaskStackListener;
 
     private CarNavigationBarView mCarNavigationBar;
     private CarNavigationBarController mController;
     private FullscreenUserSwitcher mFullscreenUserSwitcher;
 
+    private CarBatteryController mCarBatteryController;
+    private BatteryMeterView mBatteryMeterView;
+
     @Override
     public void start() {
         super.start();
         mTaskStackListener = new TaskStackListenerImpl();
         SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
         registerPackageChangeReceivers();
+
+        mCarBatteryController.startListening();
+    }
+
+    @Override
+    public void destroy() {
+        mCarBatteryController.stopListening();
+        super.destroy();
+    }
+
+    @Override
+    protected PhoneStatusBarView makeStatusBarView() {
+        PhoneStatusBarView statusBarView = super.makeStatusBarView();
+
+        mBatteryMeterView = ((BatteryMeterView) statusBarView.findViewById(R.id.battery));
+
+        // By default, the BatteryMeterView should not be visible. It will be toggled visible
+        // when a device has connected by bluetooth.
+        mBatteryMeterView.setVisibility(View.GONE);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "makeStatusBarView(). mBatteryMeterView: " + mBatteryMeterView);
+        }
+
+        return statusBarView;
+    }
+
+    @Override
+    protected BatteryController createBatteryController() {
+        mCarBatteryController = new CarBatteryController(mContext);
+        mCarBatteryController.addBatteryViewHandler(this);
+        return mCarBatteryController;
     }
 
     @Override
@@ -85,6 +123,28 @@
 
     }
 
+    @Override
+    public void showBatteryView() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
+        }
+
+        if (mBatteryMeterView != null) {
+            mBatteryMeterView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void hideBatteryView() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
+        }
+
+        if (mBatteryMeterView != null) {
+            mBatteryMeterView.setVisibility(View.GONE);
+        }
+    }
+
     private BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index a27ec28..fffb20a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -26,6 +26,7 @@
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Objects;
 
 /**
@@ -37,6 +38,7 @@
     private OnGroupChangeListener mListener;
     private int mBarState = -1;
     private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
+    private HeadsUpManager mHeadsUpManager;
 
     public void setOnGroupChangeListener(OnGroupChangeListener listener) {
         mListener = listener;
@@ -142,6 +144,9 @@
                         && group.summary.notification.getNotification().isGroupSummary()
                         && hasIsolatedChildren(group)));
         if (prevSuppressed != group.suppressed) {
+            if (group.suppressed) {
+                handleSuppressedSummaryHeadsUpped(group.summary);
+            }
             mListener.onGroupsChanged();
         }
     }
@@ -160,6 +165,15 @@
         return count;
     }
 
+    private NotificationData.Entry getIsolatedChild(String groupKey) {
+        for (StatusBarNotification sbn : mIsolatedEntries.values()) {
+            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+                return mGroupMap.get(sbn.getKey()).summary;
+            }
+        }
+        return null;
+    }
+
     public void onEntryUpdated(NotificationData.Entry entry,
             StatusBarNotification oldNotification) {
         if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
@@ -332,6 +346,9 @@
                 // it doesn't lead to an update.
                 updateSuppression(mGroupMap.get(entry.notification.getGroupKey()));
                 mListener.onGroupsChanged();
+            } else {
+                handleSuppressedSummaryHeadsUpped(entry);
+
             }
         } else {
             if (mIsolatedEntries.containsKey(sbn.getKey())) {
@@ -344,6 +361,32 @@
         }
     }
 
+    private void handleSuppressedSummaryHeadsUpped(NotificationData.Entry entry) {
+        StatusBarNotification sbn = entry.notification;
+        if (!isGroupSuppressed(sbn.getGroupKey())
+                || !sbn.getNotification().isGroupSummary()
+                || !entry.row.isHeadsUp()) {
+            return;
+        }
+        // The parent of a suppressed group got huned, lets hun the child!
+        NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
+        if (notificationGroup != null) {
+            Iterator<NotificationData.Entry> iterator = notificationGroup.children.iterator();
+            NotificationData.Entry child = iterator.hasNext() ? iterator.next() : null;
+            if (child == null) {
+                child = getIsolatedChild(sbn.getGroupKey());
+            }
+            if (child != null) {
+                if (mHeadsUpManager.isHeadsUp(child.key)) {
+                    mHeadsUpManager.updateNotification(child, true);
+                } else {
+                    mHeadsUpManager.showNotification(child);
+                }
+            }
+        }
+        mHeadsUpManager.releaseImmediately(entry.key);
+    }
+
     private boolean shouldIsolate(StatusBarNotification sbn) {
         NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
         return (sbn.isGroup() && !sbn.getNotification().isGroupSummary())
@@ -360,6 +403,10 @@
                 || notificationGroup.summary.row.getTranslationY() < 0;
     }
 
+    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+        mHeadsUpManager = headsUpManager;
+    }
+
     public static class NotificationGroup {
         public final HashSet<NotificationData.Entry> children = new HashSet<>();
         public NotificationData.Entry summary;
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 933d5bd..e52a401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -21,9 +21,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManagerNative;
-import android.app.ActivityOptions;
 import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -147,6 +145,7 @@
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+import com.android.systemui.statusbar.policy.BatteryControllerImpl;
 import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.CastControllerImpl;
@@ -730,6 +729,7 @@
         mHeadsUpManager.addListener(mGroupManager);
         mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
         mNotificationData.setHeadsUpManager(mHeadsUpManager);
+        mGroupManager.setHeadsUpManager(mHeadsUpManager);
 
         if (MULTIUSER_DEBUG) {
             mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
@@ -826,7 +826,7 @@
         // Other icons
         mLocationController = new LocationControllerImpl(mContext,
                 mHandlerThread.getLooper()); // will post a notification
-        mBatteryController = new BatteryController(mContext);
+        mBatteryController = createBatteryController();
         mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
             @Override
             public void onPowerSaveChanged(boolean isPowerSave) {
@@ -943,6 +943,10 @@
         return mStatusBarView;
     }
 
+    protected BatteryController createBatteryController() {
+        return new BatteryControllerImpl(mContext);
+    }
+
     @Override
     protected void reInflateViews() {
         super.reInflateViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index bb3e116..ea64fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -16,158 +16,33 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.util.Log;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 
-public class BatteryController extends BroadcastReceiver {
-    private static final String TAG = "BatteryController";
+public interface BatteryController {
+    /**
+     * Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}.
+     */
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 
-    public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+    /**
+     * Sets if the current device is in power save mode.
+     */
+    void setPowerSaveMode(boolean powerSave);
 
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    /**
+     * Returns {@code true} if the device is currently in power save mode.
+     */
+    boolean isPowerSave();
 
-    private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
-    private final PowerManager mPowerManager;
-    private final Handler mHandler;
+    void addStateChangedCallback(BatteryStateChangeCallback cb);
+    void removeStateChangedCallback(BatteryStateChangeCallback cb);
 
-    private int mLevel;
-    private boolean mPluggedIn;
-    private boolean mCharging;
-    private boolean mCharged;
-    private boolean mPowerSave;
-    private boolean mTestmode = false;
-
-    public BatteryController(Context context) {
-        mHandler = new Handler();
-        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
-        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
-        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
-        filter.addAction(ACTION_LEVEL_TEST);
-        context.registerReceiver(this, filter);
-
-        updatePowerSave();
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("BatteryController state:");
-        pw.print("  mLevel="); pw.println(mLevel);
-        pw.print("  mPluggedIn="); pw.println(mPluggedIn);
-        pw.print("  mCharging="); pw.println(mCharging);
-        pw.print("  mCharged="); pw.println(mCharged);
-        pw.print("  mPowerSave="); pw.println(mPowerSave);
-    }
-
-    public void setPowerSaveMode(boolean powerSave) {
-        mPowerManager.setPowerSaveMode(powerSave);
-    }
-
-    public void addStateChangedCallback(BatteryStateChangeCallback cb) {
-        mChangeCallbacks.add(cb);
-        cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
-        cb.onPowerSaveChanged(mPowerSave);
-    }
-
-    public void removeStateChangedCallback(BatteryStateChangeCallback cb) {
-        mChangeCallbacks.remove(cb);
-    }
-
-    public void onReceive(final Context context, Intent intent) {
-        final String action = intent.getAction();
-        if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
-            if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
-            mLevel = (int)(100f
-                    * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
-                    / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
-            mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
-
-            final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
-                    BatteryManager.BATTERY_STATUS_UNKNOWN);
-            mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
-            mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
-
-            fireBatteryLevelChanged();
-        } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
-            updatePowerSave();
-        } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) {
-            setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
-        } else if (action.equals(ACTION_LEVEL_TEST)) {
-            mTestmode = true;
-            mHandler.post(new Runnable() {
-                int curLevel = 0;
-                int incr = 1;
-                int saveLevel = mLevel;
-                boolean savePlugged = mPluggedIn;
-                Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
-                @Override
-                public void run() {
-                    if (curLevel < 0) {
-                        mTestmode = false;
-                        dummy.putExtra("level", saveLevel);
-                        dummy.putExtra("plugged", savePlugged);
-                        dummy.putExtra("testmode", false);
-                    } else {
-                        dummy.putExtra("level", curLevel);
-                        dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
-                                : 0);
-                        dummy.putExtra("testmode", true);
-                    }
-                    context.sendBroadcast(dummy);
-
-                    if (!mTestmode) return;
-
-                    curLevel += incr;
-                    if (curLevel == 100) {
-                        incr *= -1;
-                    }
-                    mHandler.postDelayed(this, 200);
-                }
-            });
-        }
-    }
-
-    public boolean isPowerSave() {
-        return mPowerSave;
-    }
-
-    private void updatePowerSave() {
-        setPowerSave(mPowerManager.isPowerSaveMode());
-    }
-
-    private void setPowerSave(boolean powerSave) {
-        if (powerSave == mPowerSave) return;
-        mPowerSave = powerSave;
-        if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
-        firePowerSaveChanged();
-    }
-
-    private void fireBatteryLevelChanged() {
-        final int N = mChangeCallbacks.size();
-        for (int i = 0; i < N; i++) {
-            mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
-        }
-    }
-
-    private void firePowerSaveChanged() {
-        final int N = mChangeCallbacks.size();
-        for (int i = 0; i < N; i++) {
-            mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
-        }
-    }
-
-    public interface BatteryStateChangeCallback {
+    /**
+     * A listener that will be notified whenever a change in battery level or power save mode
+     * has occurred.
+     */
+    interface BatteryStateChangeCallback {
         void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging);
         void onPowerSaveChanged(boolean isPowerSave);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
new file mode 100644
index 0000000..24207f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -0,0 +1,179 @@
+/*
+ * 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.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Default implementation of a {@link BatteryController}. This controller monitors for battery
+ * level change events that are broadcasted by the system.
+ */
+public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
+    private static final String TAG = "BatteryController";
+
+    public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+    private final PowerManager mPowerManager;
+    private final Handler mHandler;
+
+    protected int mLevel;
+    protected boolean mPluggedIn;
+    protected boolean mCharging;
+    protected boolean mCharged;
+    protected boolean mPowerSave;
+    private boolean mTestmode = false;
+
+    public BatteryControllerImpl(Context context) {
+        mHandler = new Handler();
+        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
+        filter.addAction(ACTION_LEVEL_TEST);
+        context.registerReceiver(this, filter);
+
+        updatePowerSave();
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("BatteryController state:");
+        pw.print("  mLevel="); pw.println(mLevel);
+        pw.print("  mPluggedIn="); pw.println(mPluggedIn);
+        pw.print("  mCharging="); pw.println(mCharging);
+        pw.print("  mCharged="); pw.println(mCharged);
+        pw.print("  mPowerSave="); pw.println(mPowerSave);
+    }
+
+    @Override
+    public void setPowerSaveMode(boolean powerSave) {
+        mPowerManager.setPowerSaveMode(powerSave);
+    }
+
+    @Override
+    public void addStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+        mChangeCallbacks.add(cb);
+        cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
+        cb.onPowerSaveChanged(mPowerSave);
+    }
+
+    @Override
+    public void removeStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) {
+        mChangeCallbacks.remove(cb);
+    }
+
+    @Override
+    public void onReceive(final Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+            if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
+            mLevel = (int)(100f
+                    * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
+                    / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
+            mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
+
+            final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
+                    BatteryManager.BATTERY_STATUS_UNKNOWN);
+            mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
+            mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
+
+            fireBatteryLevelChanged();
+        } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
+            updatePowerSave();
+        } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) {
+            setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
+        } else if (action.equals(ACTION_LEVEL_TEST)) {
+            mTestmode = true;
+            mHandler.post(new Runnable() {
+                int curLevel = 0;
+                int incr = 1;
+                int saveLevel = mLevel;
+                boolean savePlugged = mPluggedIn;
+                Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
+                @Override
+                public void run() {
+                    if (curLevel < 0) {
+                        mTestmode = false;
+                        dummy.putExtra("level", saveLevel);
+                        dummy.putExtra("plugged", savePlugged);
+                        dummy.putExtra("testmode", false);
+                    } else {
+                        dummy.putExtra("level", curLevel);
+                        dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
+                                : 0);
+                        dummy.putExtra("testmode", true);
+                    }
+                    context.sendBroadcast(dummy);
+
+                    if (!mTestmode) return;
+
+                    curLevel += incr;
+                    if (curLevel == 100) {
+                        incr *= -1;
+                    }
+                    mHandler.postDelayed(this, 200);
+                }
+            });
+        }
+    }
+
+    @Override
+    public boolean isPowerSave() {
+        return mPowerSave;
+    }
+
+    private void updatePowerSave() {
+        setPowerSave(mPowerManager.isPowerSaveMode());
+    }
+
+    private void setPowerSave(boolean powerSave) {
+        if (powerSave == mPowerSave) return;
+        mPowerSave = powerSave;
+        if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
+        firePowerSaveChanged();
+    }
+
+    protected void fireBatteryLevelChanged() {
+        final int N = mChangeCallbacks.size();
+        for (int i = 0; i < N; i++) {
+            mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
+        }
+    }
+
+    private void firePowerSaveChanged() {
+        final int N = mChangeCallbacks.size();
+        for (int i = 0; i < N; i++) {
+            mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index ab81712..ebefdde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -185,6 +185,11 @@
 
         if (alert) {
             HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
+            if (headsUpEntry == null) {
+                // the entry was released before this update (i.e by a listener) This can happen
+                // with the groupmanager
+                return;
+            }
             headsUpEntry.updateEntry();
             setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
         }
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index ac7872a..58e3674 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -2546,7 +2546,8 @@
                 return false;
             }
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+                    onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype,
+                    true /* forward */);
             if (nextSubtype == null) {
                 return false;
             }
@@ -2569,7 +2570,8 @@
                 return false;
             }
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+                    false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype,
+                    true /* forward */);
             if (nextSubtype == null) {
                 return false;
             }
@@ -2963,9 +2965,8 @@
 
     private void handleSwitchInputMethod(final boolean forwardDirection) {
         synchronized (mMethodMap) {
-            // TODO: Support forwardDirection.
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    false, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+                    false, mMethodMap.get(mCurMethodId), mCurrentSubtype, forwardDirection);
             if (nextSubtype == null) {
                 return;
             }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index e32d1d1..e69c662 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1447,7 +1447,8 @@
             if (stack == null || StackId.persistTaskBounds(stack.mStackId)) {
                 mLastNonFullscreenBounds = mBounds;
             }
-            mOverrideConfig = calculateOverrideConfig(mTmpRect, insetBounds);
+            mOverrideConfig = calculateOverrideConfig(mTmpRect, insetBounds,
+                    mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
         }
 
         if (mFullscreen != oldFullscreen) {
@@ -1457,33 +1458,38 @@
         return !mOverrideConfig.equals(oldConfig) ? mOverrideConfig : null;
     }
 
-    private void subtractNonDecorInsets(Rect inOutBounds, Rect inInsetBounds) {
+    private void subtractNonDecorInsets(Rect inOutBounds, Rect inInsetBounds,
+                                        boolean overrideWidth, boolean overrideHeight) {
         mTmpRect2.set(inInsetBounds);
         mService.mWindowManager.subtractNonDecorInsets(mTmpRect2);
         int leftInset = mTmpRect2.left - inInsetBounds.left;
         int topInset = mTmpRect2.top - inInsetBounds.top;
-        int rightInset = inInsetBounds.right - mTmpRect2.right;
-        int bottomInset = inInsetBounds.bottom - mTmpRect2.bottom;
+        int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect2.right;
+        int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect2.bottom;
         inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
     }
 
-    private void subtractStableInsets(Rect inOutBounds, Rect inInsetBounds) {
+    private void subtractStableInsets(Rect inOutBounds, Rect inInsetBounds,
+                                      boolean overrideWidth, boolean overrideHeight) {
         mTmpRect2.set(inInsetBounds);
         mService.mWindowManager.subtractStableInsets(mTmpRect2);
         int leftInset = mTmpRect2.left - inInsetBounds.left;
         int topInset = mTmpRect2.top - inInsetBounds.top;
-        int rightInset = inInsetBounds.right - mTmpRect2.right;
-        int bottomInset = inInsetBounds.bottom - mTmpRect2.bottom;
+        int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect2.right;
+        int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect2.bottom;
         inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
     }
 
-    private Configuration calculateOverrideConfig(Rect bounds, Rect insetBounds) {
+    private Configuration calculateOverrideConfig(Rect bounds, Rect insetBounds,
+                                                  boolean overrideWidth, boolean overrideHeight) {
         mTmpNonDecorBounds.set(bounds);
         mTmpStableBounds.set(bounds);
         subtractNonDecorInsets(
-                mTmpNonDecorBounds, insetBounds != null ? insetBounds : bounds);
+                mTmpNonDecorBounds, insetBounds != null ? insetBounds : bounds,
+                overrideWidth, overrideHeight);
         subtractStableInsets(
-                mTmpStableBounds, insetBounds != null ? insetBounds : bounds);
+                mTmpStableBounds, insetBounds != null ? insetBounds : bounds,
+                overrideWidth, overrideHeight);
 
         // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen area,
         // i.e. the screen area without the system bars.
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 7e25632..a11ee74 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -28,6 +28,8 @@
 
 import dalvik.system.VMRuntime;
 
+import java.util.Arrays;
+
 public final class Installer extends SystemService {
     private static final String TAG = "Installer";
 
@@ -90,14 +92,14 @@
         mInstaller.execute("migrate_app_data", uuid, pkgname, userid, flags);
     }
 
-    public void clearAppData(String uuid, String pkgname, int userid, int flags)
+    public void clearAppData(String uuid, String pkgname, int userid, int flags, long ceDataInode)
             throws InstallerException {
-        mInstaller.execute("clear_app_data", uuid, pkgname, userid, flags);
+        mInstaller.execute("clear_app_data", uuid, pkgname, userid, flags, ceDataInode);
     }
 
-    public void destroyAppData(String uuid, String pkgname, int userid, int flags)
+    public void destroyAppData(String uuid, String pkgname, int userid, int flags, long ceDataInode)
             throws InstallerException {
-        mInstaller.execute("destroy_app_data", uuid, pkgname, userid, flags);
+        mInstaller.execute("destroy_app_data", uuid, pkgname, userid, flags, ceDataInode);
     }
 
     public void moveCompleteApp(String from_uuid, String to_uuid, String package_name,
@@ -107,31 +109,26 @@
                 data_app_name, appid, seinfo, targetSdkVersion);
     }
 
-    public void getAppSize(String uuid, String pkgname, int userid, int flags, String apkPath,
-            String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets,
-            PackageStats pStats) throws InstallerException {
-        for (String instructionSet : instructionSets) {
-            assertValidInstructionSet(instructionSet);
-        }
-
-        // TODO: Extend getSizeInfo to look at the full subdirectory tree,
-        // not just the first level.
-        // TODO: Extend getSizeInfo to look at *all* instrution sets, not
-        // just the primary.
-        final String rawRes = mInstaller.executeForResult("get_app_size", uuid, pkgname, userid,
-                flags, apkPath, libDirPath, fwdLockApkPath, asecPath, instructionSets[0]);
-        final String res[] = rawRes.split(" ");
-
-        if ((res == null) || (res.length != 5)) {
-            throw new InstallerException("Invalid size result: " + rawRes);
-        }
+    public void getAppSize(String uuid, String pkgname, int userid, int flags, long ceDataInode,
+            String codePath, PackageStats stats) throws InstallerException {
+        final String[] res = mInstaller.execute("get_app_size", uuid, pkgname, userid, flags,
+                ceDataInode, codePath);
         try {
-            pStats.codeSize = Long.parseLong(res[1]);
-            pStats.dataSize = Long.parseLong(res[2]);
-            pStats.cacheSize = Long.parseLong(res[3]);
-            pStats.externalCodeSize = Long.parseLong(res[4]);
-        } catch (NumberFormatException e) {
-            throw new InstallerException("Invalid size result: " + rawRes);
+            stats.codeSize += Long.parseLong(res[1]);
+            stats.dataSize += Long.parseLong(res[2]);
+            stats.cacheSize += Long.parseLong(res[3]);
+        } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
+            throw new InstallerException("Invalid size result: " + Arrays.toString(res));
+        }
+    }
+
+    public long getAppDataInode(String uuid, String pkgname, int userid, int flags)
+            throws InstallerException {
+        final String[] res = mInstaller.execute("get_app_data_inode", uuid, pkgname, userid, flags);
+        try {
+            return Long.parseLong(res[1]);
+        } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
+            throw new InstallerException("Invalid inode result: " + Arrays.toString(res));
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bee276e..ddccbb3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2388,9 +2388,9 @@
                     if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
                         psit.remove();
                         logCriticalInfo(Log.WARN, "System package " + ps.name
-                                + " no longer exists; wiping its data");
-                        // No apps are running this early, so no need to freeze
-                        removeDataDirsLIF(null, ps.name);
+                                + " no longer exists; it's data will be wiped");
+                        // Actual deletion of code and data will be handled by later
+                        // reconciliation step
                     } else {
                         final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
                         if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
@@ -2403,8 +2403,13 @@
             //look for any incomplete package installations
             ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();
             for (int i = 0; i < deletePkgsList.size(); i++) {
-                // No apps are running this early, so no need to freeze
-                cleanupInstallFailedPackageLIF(deletePkgsList.get(i));
+                // Actual deletion of code and data will be handled by later
+                // reconciliation step
+                final String packageName = deletePkgsList.get(i).name;
+                logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + packageName);
+                synchronized (mPackages) {
+                    mSettings.removePackageLPw(packageName);
+                }
             }
 
             //delete tmp files
@@ -2437,9 +2442,9 @@
                     String msg;
                     if (deletedPkg == null) {
                         msg = "Updated system package " + deletedAppName
-                                + " no longer exists; wiping its data";
-                        // No apps are running this early, so no need to freeze
-                        removeDataDirsLIF(null, deletedAppName);
+                                + " no longer exists; it's data will be wiped";
+                        // Actual deletion of code and data will be handled by later
+                        // reconciliation step
                     } else {
                         msg = "Updated system app + " + deletedAppName
                                 + " no longer present; removing system privileges for "
@@ -2585,7 +2590,7 @@
             } else {
                 storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
             }
-            reconcileAppsData(StorageManager.UUID_PRIVATE_INTERNAL, UserHandle.USER_SYSTEM,
+            reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL, UserHandle.USER_SYSTEM,
                     storageFlags);
 
             // If this is first boot after an OTA, and a normal boot, then
@@ -2596,8 +2601,11 @@
                     final PackageSetting ps = mSettings.mPackages.valueAt(i);
                     if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.volumeUuid)) {
                         // No apps are running this early, so no need to freeze
-                        deleteCodeCacheDirsLIF(ps.volumeUuid, ps.name);
+                        clearAppDataLIF(ps.pkg, UserHandle.USER_ALL,
+                                StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE
+                                        | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
                     }
+                    clearAppProfilesLIF(ps.pkg);
                 }
                 ver.fingerprint = Build.FINGERPRINT;
             }
@@ -2981,24 +2989,6 @@
         }
     }
 
-    void cleanupInstallFailedPackageLIF(PackageSetting ps) {
-        logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + ps.name);
-
-        removeDataDirsLIF(ps.volumeUuid, ps.name);
-        if (ps.codePath != null) {
-            removeCodePathLI(ps.codePath);
-        }
-        if (ps.resourcePath != null && !ps.resourcePath.equals(ps.codePath)) {
-            if (ps.resourcePath.isDirectory()) {
-                FileUtils.deleteContents(ps.resourcePath);
-            }
-            ps.resourcePath.delete();
-        }
-        synchronized (mPackages) {
-            mSettings.removePackageLPw(ps.name);
-        }
-    }
-
     static int[] appendInts(int[] cur, int[] add) {
         if (add == null) return cur;
         if (cur == null) return add;
@@ -7321,23 +7311,6 @@
         return true;
     }
 
-    private boolean removeDataDirsLIF(String volumeUuid, String packageName) {
-        // TODO: triage flags as part of 26466827
-        final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
-
-        boolean res = true;
-        final int[] userIds = sUserManager.getUserIds();
-        for (int userId : userIds) {
-            try {
-                mInstaller.destroyAppData(volumeUuid, packageName, userId, flags);
-            } catch (InstallerException e) {
-                Slog.w(TAG, "Failed to delete data directory", e);
-                res = false;
-            }
-        }
-        return res;
-    }
-
     void removeCodePathLI(File codePath) {
         if (codePath.isDirectory()) {
             try {
@@ -7350,77 +7323,106 @@
         }
     }
 
-    void destroyAppDataLI(String volumeUuid, String packageName, int userId, int flags) {
-        try (PackageFreezer freezer = freezePackage(packageName, "destroyAppDataLI")) {
-            try {
-                mInstaller.destroyAppData(volumeUuid, packageName, userId, flags);
-            } catch (InstallerException e) {
-                Slog.w(TAG, "Failed to destroy app data", e);
-            }
-        }
+    private int[] resolveUserIds(int userId) {
+        return (userId == UserHandle.USER_ALL) ? sUserManager.getUserIds() : new int[] { userId };
     }
 
-    void restoreconAppDataLI(String volumeUuid, String packageName, int userId, int flags,
-            int appId, String seinfo) {
-        try {
-            mInstaller.restoreconAppData(volumeUuid, packageName, userId, flags, appId, seinfo);
-        } catch (InstallerException e) {
-            Slog.e(TAG, "Failed to restorecon for " + packageName + ": " + e);
-        }
-    }
-
-    private void deleteProfilesLIF(PackageParser.Package pkg, boolean destroy) {
-        try {
-            if (destroy) {
-                mInstaller.destroyAppProfiles(pkg.packageName);
-            } else {
-                mInstaller.clearAppProfiles(pkg.packageName);
-            }
-        } catch (InstallerException ex) {
-            Log.e(TAG, "Could not delete profiles for package " + pkg.packageName);
-        }
-    }
-
-    private void deleteCodeCacheDirsLIF(String volumeUuid, String packageName) {
-        final PackageParser.Package pkg;
-        synchronized (mPackages) {
-            pkg = mPackages.get(packageName);
-        }
+    private void clearAppDataLIF(PackageParser.Package pkg, int userId, int flags) {
         if (pkg == null) {
-            Slog.w(TAG, "Failed to delete code cache directory. No package: " + packageName);
+            Slog.wtf(TAG, "Package was null!", new Throwable());
             return;
         }
-        deleteCodeCacheDirsLIF(pkg);
+        clearAppDataLeafLIF(pkg, userId, flags);
+        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+        for (int i = 0; i < childCount; i++) {
+            clearAppDataLeafLIF(pkg.childPackages.get(i), userId, flags);
+        }
     }
 
-    private void deleteCodeCacheDirsLIF(PackageParser.Package pkg) {
-        // TODO: triage flags as part of 26466827
-        final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
-
-        int[] users = sUserManager.getUserIds();
-        int res = 0;
-        for (int user : users) {
-            // Remove the parent code cache
+    private void clearAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags) {
+        final PackageSetting ps;
+        synchronized (mPackages) {
+            ps = mSettings.mPackages.get(pkg.packageName);
+        }
+        for (int realUserId : resolveUserIds(userId)) {
+            final long ceDataInode = (ps != null) ? ps.getCeDataInode(realUserId) : 0;
             try {
-                mInstaller.clearAppData(pkg.volumeUuid, pkg.packageName, user,
-                        flags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+                mInstaller.clearAppData(pkg.volumeUuid, pkg.packageName, realUserId, flags,
+                        ceDataInode);
             } catch (InstallerException e) {
-                Slog.w(TAG, "Failed to delete code cache directory", e);
+                Slog.w(TAG, String.valueOf(e));
             }
-            final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
-            for (int i = 0; i < childCount; i++) {
-                PackageParser.Package childPkg = pkg.childPackages.get(i);
-                // Remove the child code cache
-                try {
-                    mInstaller.clearAppData(childPkg.volumeUuid, childPkg.packageName,
-                            user, flags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-                } catch (InstallerException e) {
-                    Slog.w(TAG, "Failed to delete code cache directory", e);
-                }
+        }
+    }
+
+    private void destroyAppDataLIF(PackageParser.Package pkg, int userId, int flags) {
+        if (pkg == null) {
+            Slog.wtf(TAG, "Package was null!", new Throwable());
+            return;
+        }
+        destroyAppDataLeafLIF(pkg, userId, flags);
+        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+        for (int i = 0; i < childCount; i++) {
+            destroyAppDataLeafLIF(pkg.childPackages.get(i), userId, flags);
+        }
+    }
+
+    private void destroyAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags) {
+        final PackageSetting ps;
+        synchronized (mPackages) {
+            ps = mSettings.mPackages.get(pkg.packageName);
+        }
+        for (int realUserId : resolveUserIds(userId)) {
+            final long ceDataInode = (ps != null) ? ps.getCeDataInode(realUserId) : 0;
+            try {
+                mInstaller.destroyAppData(pkg.volumeUuid, pkg.packageName, realUserId, flags,
+                        ceDataInode);
+            } catch (InstallerException e) {
+                Slog.w(TAG, String.valueOf(e));
             }
         }
     }
 
+    private void destroyAppProfilesLIF(PackageParser.Package pkg) {
+        if (pkg == null) {
+            Slog.wtf(TAG, "Package was null!", new Throwable());
+            return;
+        }
+        destroyAppProfilesLeafLIF(pkg);
+        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+        for (int i = 0; i < childCount; i++) {
+            destroyAppProfilesLeafLIF(pkg.childPackages.get(i));
+        }
+    }
+
+    private void destroyAppProfilesLeafLIF(PackageParser.Package pkg) {
+        try {
+            mInstaller.destroyAppProfiles(pkg.packageName);
+        } catch (InstallerException e) {
+            Slog.w(TAG, String.valueOf(e));
+        }
+    }
+
+    private void clearAppProfilesLIF(PackageParser.Package pkg) {
+        if (pkg == null) {
+            Slog.wtf(TAG, "Package was null!", new Throwable());
+            return;
+        }
+        clearAppProfilesLeafLIF(pkg);
+        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+        for (int i = 0; i < childCount; i++) {
+            clearAppProfilesLeafLIF(pkg.childPackages.get(i));
+        }
+    }
+
+    private void clearAppProfilesLeafLIF(PackageParser.Package pkg) {
+        try {
+            mInstaller.clearAppProfiles(pkg.packageName);
+        } catch (InstallerException e) {
+            Slog.w(TAG, String.valueOf(e));
+        }
+    }
+
     private void setInstallAndUpdateTime(PackageParser.Package pkg, long firstInstallTime,
             long lastUpdateTime) {
         // Set parent install/update time
@@ -7607,7 +7609,9 @@
         } finally {
             if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
                 // DELETE_DATA_ON_FAILURES is only used by frozen paths
-                removeDataDirsLIF(pkg.volumeUuid, pkg.packageName);
+                destroyAppDataLIF(pkg, UserHandle.USER_ALL,
+                        StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+                destroyAppProfilesLIF(pkg);
             }
         }
     }
@@ -11239,7 +11243,10 @@
 
             if (installed) {
                 if (pkgSetting.pkg != null) {
-                    prepareAppDataAfterInstall(pkgSetting.pkg);
+                    synchronized (mInstallLock) {
+                        // We don't need to freeze for a brand new install
+                        prepareAppDataAfterInstallLIF(pkgSetting.pkg);
+                    }
                 }
                 sendPackageAddedForUser(packageName, pkgSetting, userId);
             }
@@ -13365,7 +13372,12 @@
             synchronized (mInstallLock) {
                 // Clean up both app data and code
                 // All package moves are frozen until finished
-                removeDataDirsLIF(volumeUuid, move.packageName);
+                try {
+                    mInstaller.destroyAppData(volumeUuid, move.packageName, UserHandle.USER_ALL,
+                            StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE, 0);
+                } catch (InstallerException e) {
+                    Slog.w(TAG, String.valueOf(e));
+                }
                 removeCodePathLI(codeFile);
             }
             return true;
@@ -13546,7 +13558,7 @@
             updateSettingsLI(newPackage, installerPackageName, null, res, user);
 
             if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
-                prepareAppDataAfterInstall(newPackage);
+                prepareAppDataAfterInstallLIF(newPackage);
 
             } else {
                 // Remove package from internal structures, but keep around any
@@ -13732,8 +13744,9 @@
                 sendResourcesChangedBroadcast(false, true, pkgList, uidArray, null);
             }
 
-            deleteCodeCacheDirsLIF(pkg);
-            deleteProfilesLIF(pkg, /*destroy*/ false);
+            clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
+                    | StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+            clearAppProfilesLIF(pkg);
 
             try {
                 final PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags,
@@ -13760,7 +13773,7 @@
                         childPs.oldCodePaths = ps.oldCodePaths;
                     }
                 }
-                prepareAppDataAfterInstall(newPackage);
+                prepareAppDataAfterInstallLIF(newPackage);
                 addedPkg = true;
             } catch (PackageManagerException e) {
                 res.setError("Package couldn't be installed in " + pkg.codePath, e);
@@ -13864,8 +13877,9 @@
         }
 
         // Successfully disabled the old package. Now proceed with re-installation
-        deleteCodeCacheDirsLIF(pkg);
-        deleteProfilesLIF(pkg, /*destroy*/ false);
+        clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
+                | StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+        clearAppProfilesLIF(pkg);
 
         res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
         pkg.setApplicationInfoFlags(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP,
@@ -13921,7 +13935,7 @@
                 }
 
                 updateSettingsLI(newPackage, installerPackageName, allUsers, res, user);
-                prepareAppDataAfterInstall(newPackage);
+                prepareAppDataAfterInstallLIF(newPackage);
             }
         } catch (PackageManagerException e) {
             res.setReturnCode(INSTALL_FAILED_INTERNAL_ERROR);
@@ -15074,11 +15088,12 @@
             PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
         String packageName = ps.name;
         if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
-        removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
         // Retrieve object to delete permissions for shared user later on
+        final PackageParser.Package deletedPkg;
         final PackageSetting deletedPs;
         // reader
         synchronized (mPackages) {
+            deletedPkg = mPackages.get(packageName);
             deletedPs = mSettings.mPackages.get(packageName);
             if (outInfo != null) {
                 outInfo.removedPackage = packageName;
@@ -15087,13 +15102,19 @@
                         : null;
             }
         }
-        if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
-            removeDataDirsLIF(ps.volumeUuid, ps.name);
+
+        removePackageLI(ps, (flags & REMOVE_CHATTY) != 0);
+
+        if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
+            destroyAppDataLIF(deletedPkg, UserHandle.USER_ALL,
+                    StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+            destroyAppProfilesLIF(deletedPkg);
             if (outInfo != null) {
                 outInfo.dataRemoved = true;
             }
             schedulePackageCleaning(packageName, UserHandle.USER_ALL, true);
         }
+
         // writer
         synchronized (mPackages) {
             if (deletedPs != null) {
@@ -15268,7 +15289,7 @@
             return false;
         }
 
-        prepareAppDataAfterInstall(newPkg);
+        prepareAppDataAfterInstallLIF(newPkg);
 
         // writer
         synchronized (mPackages) {
@@ -15594,7 +15615,7 @@
             if (DEBUG_REMOVE) {
                 Slog.d(TAG, "Marking package:" + ps.name + " uninstalled for user:" + nextUserId);
             }
-            ps.setUserState(nextUserId, COMPONENT_ENABLED_STATE_DEFAULT,
+            ps.setUserState(nextUserId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
                     false /*installed*/, true /*stopped*/, true /*notLaunched*/,
                     false /*hidden*/, false /*suspended*/, null, null, null,
                     false /*blockUninstall*/,
@@ -15604,6 +15625,11 @@
 
     private boolean clearPackageStateForUserLIF(PackageSetting ps, int userId,
             PackageRemovedInfo outInfo) {
+        final PackageParser.Package pkg;
+        synchronized (mPackages) {
+            pkg = mPackages.get(ps.name);
+        }
+
         final int[] userIds = (userId == UserHandle.USER_ALL) ? sUserManager.getUserIds()
                 : new int[] {userId};
         for (int nextUserId : userIds) {
@@ -15611,13 +15637,9 @@
                 Slog.d(TAG, "Updating package:" + ps.name + " install state for user:"
                         + nextUserId);
             }
-            final int flags =  StorageManager.FLAG_STORAGE_CE|  StorageManager.FLAG_STORAGE_DE;
-            try {
-                mInstaller.destroyAppData(ps.volumeUuid, ps.name, nextUserId, flags);
-            } catch (InstallerException e) {
-                Slog.w(TAG, "Couldn't remove cache files for package " + ps.name, e);
-                return false;
-            }
+
+            destroyAppDataLIF(pkg, userId,
+                    StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
             removeKeystoreDataIfNeeded(nextUserId, ps.appId);
             schedulePackageCleaning(ps.name, nextUserId, false);
             synchronized (mPackages) {
@@ -15654,6 +15676,8 @@
     }
 
     private void clearExternalStorageDataSync(String packageName, int userId, boolean allData) {
+        if (DEFAULT_CONTAINER_PACKAGE.equals(packageName)) return;
+
         final boolean mounted;
         if (Environment.isExternalStorageEmulated()) {
             mounted = true;
@@ -15713,14 +15737,15 @@
     @Override
     public void clearApplicationProfileData(String packageName) {
         enforceSystemOrRoot("Only the system can clear all profile data");
-        synchronized (mInstallLock) {
-            try (PackageFreezer freezer = freezePackage(packageName,
-                    "clearApplicationProfileData")) {
-                try {
-                    mInstaller.clearAppProfiles(packageName);
-                } catch (InstallerException ex) {
-                    Log.e(TAG, "Could not clear profile data of package " + packageName);
-                }
+
+        final PackageParser.Package pkg;
+        synchronized (mPackages) {
+            pkg = mPackages.get(packageName);
+        }
+
+        try (PackageFreezer freezer = freezePackage(packageName, "clearApplicationProfileData")) {
+            synchronized (mInstallLock) {
+                clearAppProfilesLIF(pkg);
             }
         }
     }
@@ -15744,13 +15769,13 @@
             public void run() {
                 mHandler.removeCallbacks(this);
                 final boolean succeeded;
-                synchronized (mInstallLock) {
-                    try (PackageFreezer freezer = freezePackage(packageName,
-                            "clearApplicationUserData")) {
+                try (PackageFreezer freezer = freezePackage(packageName,
+                        "clearApplicationUserData")) {
+                    synchronized (mInstallLock) {
                         succeeded = clearApplicationUserDataLIF(packageName, userId);
                     }
+                    clearExternalStorageDataSync(packageName, userId, true);
                 }
-                clearExternalStorageDataSync(packageName, userId, true);
                 if (succeeded) {
                     // invoke DeviceStorageMonitor's update method to clear any notifications
                     DeviceStorageMonitorInternal dsm = LocalServices
@@ -15796,35 +15821,22 @@
             resetUserChangesToRuntimePermissionsAndFlagsLPw(ps, userId);
         }
 
-        // Always delete data directories for package, even if we found no other
-        // record of app. This helps users recover from UID mismatches without
-        // resorting to a full data wipe.
-        // TODO: triage flags as part of 26466827
-        final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
-        try {
-            mInstaller.clearAppData(pkg.volumeUuid, packageName, userId, flags);
-        } catch (InstallerException e) {
-            Slog.w(TAG, "Couldn't remove cache files for package " + packageName, e);
-            return false;
-        }
+        clearAppDataLIF(pkg, userId,
+                StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
 
         final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
         removeKeystoreDataIfNeeded(userId, appId);
 
-        // Create a native library symlink only if we have native libraries
-        // and if the native libraries are 32 bit libraries. We do not provide
-        // this symlink for 64 bit libraries.
-        if (pkg.applicationInfo.primaryCpuAbi != null &&
-                !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) {
-            final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
-            try {
-                mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName,
-                        nativeLibPath, userId);
-            } catch (InstallerException e) {
-                Slog.w(TAG, "Failed linking native library dir", e);
-                return false;
-            }
+        final UserManager um = mContext.getSystemService(UserManager.class);
+        final int flags;
+        if (um.isUserUnlocked(userId)) {
+            flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
+        } else if (um.isUserRunning(userId)) {
+            flags = StorageManager.FLAG_STORAGE_DE;
+        } else {
+            flags = 0;
         }
+        prepareAppDataContentsLIF(pkg, userId, flags);
 
         return true;
     }
@@ -15994,56 +16006,35 @@
                 android.Manifest.permission.DELETE_CACHE_FILES, null);
         // Queue up an async operation since the package deletion may take a little while.
         final int userId = UserHandle.getCallingUserId();
+
+        final PackageParser.Package pkg;
+        synchronized (mPackages) {
+            pkg = mPackages.get(packageName);
+        }
+
         mHandler.post(new Runnable() {
             public void run() {
-                mHandler.removeCallbacks(this);
-                final boolean succeded;
-                synchronized (mInstallLock) {
-                    succeded = deleteApplicationCacheFilesLI(packageName, userId);
+                try (PackageFreezer freezer = freezePackage(packageName,
+                        "deleteApplicationCacheFiles")) {
+                    synchronized (mInstallLock) {
+                        final int flags = StorageManager.FLAG_STORAGE_DE
+                                | StorageManager.FLAG_STORAGE_CE;
+                        clearAppDataLIF(pkg, userId, flags | Installer.FLAG_CLEAR_CACHE_ONLY);
+                        clearAppDataLIF(pkg, userId, flags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+                    }
+                    clearExternalStorageDataSync(packageName, userId, false);
                 }
-                clearExternalStorageDataSync(packageName, userId, false);
                 if (observer != null) {
                     try {
-                        observer.onRemoveCompleted(packageName, succeded);
+                        observer.onRemoveCompleted(packageName, true);
                     } catch (RemoteException e) {
                         Log.i(TAG, "Observer no longer exists.");
                     }
-                } //end if observer
-            } //end run
+                }
+            }
         });
     }
 
-    private boolean deleteApplicationCacheFilesLI(String packageName, int userId) {
-        if (packageName == null) {
-            Slog.w(TAG, "Attempt to delete null packageName.");
-            return false;
-        }
-        PackageParser.Package p;
-        synchronized (mPackages) {
-            p = mPackages.get(packageName);
-        }
-        if (p == null) {
-            Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
-            return false;
-        }
-        final ApplicationInfo applicationInfo = p.applicationInfo;
-        if (applicationInfo == null) {
-            Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
-            return false;
-        }
-        // TODO: triage flags as part of 26466827
-        final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
-        try {
-            mInstaller.clearAppData(p.volumeUuid, packageName, userId,
-                    flags | Installer.FLAG_CLEAR_CACHE_ONLY);
-        } catch (InstallerException e) {
-            Slog.w(TAG, "Couldn't remove cache files for package "
-                    + packageName + " u" + userId, e);
-            return false;
-        }
-        return true;
-    }
-
     @Override
     public void getPackageSizeInfo(final String packageName, int userHandle,
             final IPackageStatsObserver observer) {
@@ -16064,90 +16055,24 @@
         mHandler.sendMessage(msg);
     }
 
-    private boolean getPackageSizeInfoLI(String packageName, int userHandle,
-            PackageStats pStats) {
-        if (packageName == null) {
-            Slog.w(TAG, "Attempt to get size of null packageName.");
-            return false;
-        }
-        PackageParser.Package p;
-        boolean dataOnly = false;
-        String libDirRoot = null;
-        String asecPath = null;
-        PackageSetting ps = null;
+    private boolean getPackageSizeInfoLI(String packageName, int userId, PackageStats stats) {
+        final PackageSetting ps;
         synchronized (mPackages) {
-            p = mPackages.get(packageName);
             ps = mSettings.mPackages.get(packageName);
-            if(p == null) {
-                dataOnly = true;
-                if((ps == null) || (ps.pkg == null)) {
-                    Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
-                    return false;
-                }
-                p = ps.pkg;
-            }
-            if (ps != null) {
-                libDirRoot = ps.legacyNativeLibraryPathString;
-            }
-            if (p != null && (p.isForwardLocked() || p.applicationInfo.isExternalAsec())) {
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    String secureContainerId = cidFromCodePath(p.applicationInfo.getBaseCodePath());
-                    if (secureContainerId != null) {
-                        asecPath = PackageHelper.getSdFilesystem(secureContainerId);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-            }
-        }
-        String publicSrcDir = null;
-        if(!dataOnly) {
-            final ApplicationInfo applicationInfo = p.applicationInfo;
-            if (applicationInfo == null) {
-                Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
+            if (ps == null) {
+                Slog.w(TAG, "Failed to find settings for " + packageName);
                 return false;
             }
-            if (p.isForwardLocked()) {
-                publicSrcDir = applicationInfo.getBaseResourcePath();
-            }
         }
-        // TODO: extend to measure size of split APKs
-        // TODO(multiArch): Extend getSizeInfo to look at the full subdirectory tree,
-        // not just the first level.
-        // TODO(multiArch): Extend getSizeInfo to look at *all* instruction sets, not
-        // just the primary.
-        String[] dexCodeInstructionSets = getDexCodeInstructionSets(getAppDexInstructionSets(ps));
-
-        String apkPath;
-        File packageDir = new File(p.codePath);
-
-        if (packageDir.isDirectory() && p.canHaveOatDir()) {
-            apkPath = packageDir.getAbsolutePath();
-            // If libDirRoot is inside a package dir, set it to null to avoid it being counted twice
-            if (libDirRoot != null && libDirRoot.startsWith(apkPath)) {
-                libDirRoot = null;
-            }
-        } else {
-            apkPath = p.baseCodePath;
-        }
-
-        // TODO: triage flags as part of 26466827
-        final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
         try {
-            mInstaller.getAppSize(p.volumeUuid, packageName, userHandle, flags, apkPath,
-                    libDirRoot, publicSrcDir, asecPath, dexCodeInstructionSets, pStats);
+            mInstaller.getAppSize(ps.volumeUuid, packageName, userId,
+                    StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE,
+                    ps.getCeDataInode(userId), ps.codePathString, stats);
+            return true;
         } catch (InstallerException e) {
+            Slog.w(TAG, String.valueOf(e));
             return false;
         }
-
-        // Fix-up for forward-locked applications in ASEC containers.
-        if (!isExternal(p)) {
-            pStats.codeSize += pStats.externalCodeSize;
-            pStats.externalCodeSize = 0L;
-        }
-
-        return true;
     }
 
     private int getUidTargetSdkVersionLockedLPr(int uid) {
@@ -18553,7 +18478,9 @@
                 }
 
                 if (!Build.FINGERPRINT.equals(ver.fingerprint)) {
-                    deleteCodeCacheDirsLIF(ps.volumeUuid, ps.name);
+                    clearAppDataLIF(ps.pkg, UserHandle.USER_ALL,
+                            StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE
+                                    | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
                 }
             }
         }
@@ -18572,7 +18499,9 @@
             }
 
             sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber, flags);
-            reconcileAppsData(volumeUuid, user.id, flags);
+            synchronized (mInstallLock) {
+                reconcileAppsDataLI(volumeUuid, user.id, flags);
+            }
         }
 
         synchronized (mPackages) {
@@ -18764,7 +18693,9 @@
         final StorageManager storage = mContext.getSystemService(StorageManager.class);
         for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
             final String volumeUuid = vol.getFsUuid();
-            reconcileAppsData(volumeUuid, userId, flags);
+            synchronized (mInstallLock) {
+                reconcileAppsDataLI(volumeUuid, userId, flags);
+            }
         }
     }
 
@@ -18777,7 +18708,7 @@
      * Verifies that directories exist and that ownership and labeling is
      * correct for all installed apps.
      */
-    private void reconcileAppsData(String volumeUuid, int userId, int flags) {
+    private void reconcileAppsDataLI(String volumeUuid, int userId, int flags) {
         Slog.v(TAG, "reconcileAppsData for " + volumeUuid + " u" + userId + " 0x"
                 + Integer.toHexString(flags));
 
@@ -18804,9 +18735,11 @@
                     assertPackageKnownAndInstalled(volumeUuid, packageName, userId);
                 } catch (PackageManagerException e) {
                     logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
-                    synchronized (mInstallLock) {
-                        destroyAppDataLI(volumeUuid, packageName, userId,
-                                StorageManager.FLAG_STORAGE_CE);
+                    try {
+                        mInstaller.destroyAppData(volumeUuid, packageName, userId,
+                                StorageManager.FLAG_STORAGE_CE, 0);
+                    } catch (InstallerException e2) {
+                        logCriticalInfo(Log.WARN, "Failed to destroy: " + e2);
                     }
                 }
             }
@@ -18821,9 +18754,11 @@
                     assertPackageKnownAndInstalled(volumeUuid, packageName, userId);
                 } catch (PackageManagerException e) {
                     logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
-                    synchronized (mInstallLock) {
-                        destroyAppDataLI(volumeUuid, packageName, userId,
-                                StorageManager.FLAG_STORAGE_DE);
+                    try {
+                        mInstaller.destroyAppData(volumeUuid, packageName, userId,
+                                StorageManager.FLAG_STORAGE_DE, 0);
+                    } catch (InstallerException e2) {
+                        logCriticalInfo(Log.WARN, "Failed to destroy: " + e2);
                     }
                 }
             }
@@ -18846,12 +18781,12 @@
             }
 
             if (ps.getInstalled(userId)) {
-                prepareAppData(volumeUuid, userId, flags, ps.pkg, restoreconNeeded);
+                prepareAppDataLIF(ps.pkg, userId, flags, restoreconNeeded);
 
-                if (maybeMigrateAppData(volumeUuid, userId, ps.pkg)) {
+                if (maybeMigrateAppDataLIF(ps.pkg, userId)) {
                     // We may have just shuffled around app data directories, so
                     // prepare them one more time
-                    prepareAppData(volumeUuid, userId, flags, ps.pkg, restoreconNeeded);
+                    prepareAppDataLIF(ps.pkg, userId, flags, restoreconNeeded);
                 }
 
                 preparedCount++;
@@ -18883,16 +18818,7 @@
      * <p>
      * <em>Note: To avoid a deadlock, do not call this method with {@code mPackages} lock held</em>
      */
-    private void prepareAppDataAfterInstall(PackageParser.Package pkg) {
-        prepareAppDataAfterInstallInternal(pkg);
-        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
-        for (int i = 0; i < childCount; i++) {
-            PackageParser.Package childPackage = pkg.childPackages.get(i);
-            prepareAppDataAfterInstallInternal(childPackage);
-        }
-    }
-
-    private void prepareAppDataAfterInstallInternal(PackageParser.Package pkg) {
+    private void prepareAppDataAfterInstallLIF(PackageParser.Package pkg) {
         final PackageSetting ps;
         synchronized (mPackages) {
             ps = mSettings.mPackages.get(pkg.packageName);
@@ -18913,7 +18839,7 @@
             if (ps.getInstalled(user.id)) {
                 // Whenever an app changes, force a restorecon of its data
                 // TODO: when user data is locked, mark that we're still dirty
-                prepareAppData(pkg.volumeUuid, user.id, flags, pkg, true);
+                prepareAppDataLIF(pkg, user.id, flags, true);
             }
         }
     }
@@ -18926,57 +18852,112 @@
      * will try recovering system apps by wiping data; third-party app data is
      * left intact.
      */
-    private void prepareAppData(String volumeUuid, int userId, int flags,
-            PackageParser.Package pkg, boolean restoreconNeeded) {
+    private void prepareAppDataLIF(PackageParser.Package pkg, int userId, int flags,
+            boolean restoreconNeeded) {
+        if (pkg == null) {
+            Slog.wtf(TAG, "Package was null!", new Throwable());
+            return;
+        }
+        prepareAppDataLeafLIF(pkg, userId, flags, restoreconNeeded);
+        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+        for (int i = 0; i < childCount; i++) {
+            prepareAppDataLeafLIF(pkg.childPackages.get(i), userId, flags, restoreconNeeded);
+        }
+    }
+
+    private void prepareAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags,
+            boolean restoreconNeeded) {
         if (DEBUG_APP_DATA) {
             Slog.v(TAG, "prepareAppData for " + pkg.packageName + " u" + userId + " 0x"
                     + Integer.toHexString(flags) + (restoreconNeeded ? " restoreconNeeded" : ""));
         }
 
+        final String volumeUuid = pkg.volumeUuid;
         final String packageName = pkg.packageName;
         final ApplicationInfo app = pkg.applicationInfo;
         final int appId = UserHandle.getAppId(app.uid);
 
         Preconditions.checkNotNull(app.seinfo);
 
-        synchronized (mInstallLock) {
-            try {
-                mInstaller.createAppData(volumeUuid, packageName, userId, flags,
-                        appId, app.seinfo, app.targetSdkVersion);
-            } catch (InstallerException e) {
-                if (app.isSystemApp()) {
-                    logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
-                            + ", but trying to recover: " + e);
-                    destroyAppDataLI(volumeUuid, packageName, userId, flags);
-                    try {
-                        mInstaller.createAppData(volumeUuid, packageName, userId, flags,
-                                appId, app.seinfo, app.targetSdkVersion);
-                        logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
-                    } catch (InstallerException e2) {
-                        logCriticalInfo(Log.DEBUG, "Recovery failed!");
-                    }
-                } else {
-                    Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
+        try {
+            mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+                    appId, app.seinfo, app.targetSdkVersion);
+        } catch (InstallerException e) {
+            if (app.isSystemApp()) {
+                logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
+                        + ", but trying to recover: " + e);
+                destroyAppDataLeafLIF(pkg, userId, flags);
+                try {
+                    mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+                            appId, app.seinfo, app.targetSdkVersion);
+                    logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
+                } catch (InstallerException e2) {
+                    logCriticalInfo(Log.DEBUG, "Recovery failed!");
                 }
+            } else {
+                Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
             }
+        }
 
-            if (restoreconNeeded) {
-                restoreconAppDataLI(volumeUuid, packageName, userId, flags, appId, app.seinfo);
+        if (restoreconNeeded) {
+            try {
+                mInstaller.restoreconAppData(volumeUuid, packageName, userId, flags, appId,
+                        app.seinfo);
+            } catch (InstallerException e) {
+                Slog.e(TAG, "Failed to restorecon for " + packageName + ": " + e);
             }
+        }
 
-            if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
-                // Create a native library symlink only if we have native libraries
-                // and if the native libraries are 32 bit libraries. We do not provide
-                // this symlink for 64 bit libraries.
-                if (app.primaryCpuAbi != null && !VMRuntime.is64BitAbi(app.primaryCpuAbi)) {
-                    final String nativeLibPath = app.nativeLibraryDir;
-                    try {
-                        mInstaller.linkNativeLibraryDirectory(volumeUuid, packageName,
-                                nativeLibPath, userId);
-                    } catch (InstallerException e) {
-                        Slog.e(TAG, "Failed to link native for " + packageName + ": " + e);
+        if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
+            try {
+                // CE storage is unlocked right now, so read out the inode and
+                // remember for use later when it's locked
+                // TODO: mark this structure as dirty so we persist it!
+                final long ceDataInode = mInstaller.getAppDataInode(volumeUuid, packageName, userId,
+                        StorageManager.FLAG_STORAGE_CE);
+                synchronized (mPackages) {
+                    final PackageSetting ps = mSettings.mPackages.get(packageName);
+                    if (ps != null) {
+                        ps.setCeDataInode(ceDataInode, userId);
                     }
                 }
+            } catch (InstallerException e) {
+                Slog.e(TAG, "Failed to find inode for " + packageName + ": " + e);
+            }
+        }
+
+        prepareAppDataContentsLeafLIF(pkg, userId, flags);
+    }
+
+    private void prepareAppDataContentsLIF(PackageParser.Package pkg, int userId, int flags) {
+        if (pkg == null) {
+            Slog.wtf(TAG, "Package was null!", new Throwable());
+            return;
+        }
+        prepareAppDataContentsLeafLIF(pkg, userId, flags);
+        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
+        for (int i = 0; i < childCount; i++) {
+            prepareAppDataContentsLeafLIF(pkg.childPackages.get(i), userId, flags);
+        }
+    }
+
+    private void prepareAppDataContentsLeafLIF(PackageParser.Package pkg, int userId, int flags) {
+        final String volumeUuid = pkg.volumeUuid;
+        final String packageName = pkg.packageName;
+        final ApplicationInfo app = pkg.applicationInfo;
+
+        if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
+            // Create a native library symlink only if we have native libraries
+            // and if the native libraries are 32 bit libraries. We do not provide
+            // this symlink for 64 bit libraries.
+            if (app.primaryCpuAbi != null && !VMRuntime.is64BitAbi(app.primaryCpuAbi)) {
+                final String nativeLibPath = app.nativeLibraryDir;
+                try {
+                    mInstaller.linkNativeLibraryDirectory(volumeUuid, packageName,
+                            nativeLibPath, userId);
+                } catch (InstallerException e) {
+                    Slog.e(TAG, "Failed to link native for " + packageName + ": " + e);
+                }
             }
         }
     }
@@ -18986,18 +18967,17 @@
      * CE/DE data to match the {@code defaultToDeviceProtectedStorage} flag
      * requested by the app.
      */
-    private boolean maybeMigrateAppData(String volumeUuid, int userId, PackageParser.Package pkg) {
+    private boolean maybeMigrateAppDataLIF(PackageParser.Package pkg, int userId) {
         if (pkg.isSystemApp() && !StorageManager.isFileEncryptedNativeOrEmulated()
                 && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
             final int storageTarget = pkg.applicationInfo.isDefaultToDeviceProtectedStorage()
                     ? StorageManager.FLAG_STORAGE_DE : StorageManager.FLAG_STORAGE_CE;
-            synchronized (mInstallLock) {
-                try {
-                    mInstaller.migrateAppData(volumeUuid, pkg.packageName, userId, storageTarget);
-                } catch (InstallerException e) {
-                    logCriticalInfo(Log.WARN,
-                            "Failed to migrate " + pkg.packageName + ": " + e.getMessage());
-                }
+            try {
+                mInstaller.migrateAppData(pkg.volumeUuid, pkg.packageName, userId,
+                        storageTarget);
+            } catch (InstallerException e) {
+                logCriticalInfo(Log.WARN,
+                        "Failed to migrate " + pkg.packageName + ": " + e.getMessage());
             }
             return true;
         } else {
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 2a96562..9d04472 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -323,6 +323,14 @@
         return res;
     }
 
+    long getCeDataInode(int userId) {
+        return readUserState(userId).ceDataInode;
+    }
+
+    void setCeDataInode(long ceDataInode, int userId) {
+        modifyUserState(userId).ceDataInode = ceDataInode;
+    }
+
     boolean getStopped(int userId) {
         return readUserState(userId).stopped;
     }
@@ -363,12 +371,13 @@
         modifyUserState(userId).blockUninstall = blockUninstall;
     }
 
-    void setUserState(int userId, int enabled, boolean installed, boolean stopped,
+    void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
             boolean notLaunched, boolean hidden, boolean suspended,
             String lastDisableAppCaller, ArraySet<String> enabledComponents,
             ArraySet<String> disabledComponents, boolean blockUninstall, int domainVerifState,
             int linkGeneration) {
         PackageUserState state = modifyUserState(userId);
+        state.ceDataInode = ceDataInode;
         state.enabled = enabled;
         state.installed = installed;
         state.stopped = stopped;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 8f7cd5e..847f993 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -195,23 +195,26 @@
     private static final String ATTR_NAME = "name";
     private static final String ATTR_USER = "user";
     private static final String ATTR_CODE = "code";
-    private static final String ATTR_NOT_LAUNCHED = "nl";
-    private static final String ATTR_ENABLED = "enabled";
     private static final String ATTR_GRANTED = "granted";
     private static final String ATTR_FLAGS = "flags";
-    private static final String ATTR_ENABLED_CALLER = "enabledCaller";
+
+    private static final String ATTR_CE_DATA_INODE = "ceDataInode";
+    private static final String ATTR_INSTALLED = "inst";
     private static final String ATTR_STOPPED = "stopped";
+    private static final String ATTR_NOT_LAUNCHED = "nl";
     // Legacy, here for reading older versions of the package-restrictions.
     private static final String ATTR_BLOCKED = "blocked";
     // New name for the above attribute.
     private static final String ATTR_HIDDEN = "hidden";
     private static final String ATTR_SUSPENDED = "suspended";
-    private static final String ATTR_INSTALLED = "inst";
     private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall";
+    private static final String ATTR_ENABLED = "enabled";
+    private static final String ATTR_ENABLED_CALLER = "enabledCaller";
     private static final String ATTR_DOMAIN_VERIFICATON_STATE = "domainVerificationStatus";
+    private static final String ATTR_APP_LINK_GENERATION = "app-link-generation";
+
     private static final String ATTR_PACKAGE_NAME = "packageName";
     private static final String ATTR_FINGERPRINT = "fingerprint";
-    private static final String ATTR_APP_LINK_GENERATION = "app-link-generation";
     private static final String ATTR_VOLUME_UUID = "volumeUuid";
     private static final String ATTR_SDK_VERSION = "sdkVersion";
     private static final String ATTR_DATABASE_VERSION = "databaseVersion";
@@ -790,7 +793,7 @@
                                     || (installUserId == UserHandle.USER_ALL
                                         && !isAdbInstallDisallowed(userManager, user.id))
                                     || installUserId == user.id;
-                            p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT,
+                            p.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT,
                                     installed,
                                     true, // stopped,
                                     true, // notLaunched
@@ -1574,7 +1577,7 @@
                     // in the stopped state, but not at first boot.  Also
                     // consider all applications to be installed.
                     for (PackageSetting pkg : mPackages.values()) {
-                        pkg.setUserState(userId, COMPONENT_ENABLED_STATE_DEFAULT,
+                        pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
                                 true,   // installed
                                 false,  // stopped
                                 false,  // notLaunched
@@ -1626,17 +1629,16 @@
                         XmlUtils.skipCurrentTag(parser);
                         continue;
                     }
-                    final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
-                    final int enabled = enabledStr == null
-                            ? COMPONENT_ENABLED_STATE_DEFAULT : Integer.parseInt(enabledStr);
-                    final String enabledCaller = parser.getAttributeValue(null,
-                            ATTR_ENABLED_CALLER);
-                    final String installedStr = parser.getAttributeValue(null, ATTR_INSTALLED);
-                    final boolean installed = installedStr == null
-                            ? true : Boolean.parseBoolean(installedStr);
-                    final String stoppedStr = parser.getAttributeValue(null, ATTR_STOPPED);
-                    final boolean stopped = stoppedStr == null
-                            ? false : Boolean.parseBoolean(stoppedStr);
+
+                    final long ceDataInode = XmlUtils.readLongAttribute(parser, ATTR_CE_DATA_INODE,
+                            0);
+                    final boolean installed = XmlUtils.readBooleanAttribute(parser, ATTR_INSTALLED,
+                            true);
+                    final boolean stopped = XmlUtils.readBooleanAttribute(parser, ATTR_STOPPED,
+                            false);
+                    final boolean notLaunched = XmlUtils.readBooleanAttribute(parser,
+                            ATTR_NOT_LAUNCHED, false);
+
                     // For backwards compatibility with the previous name of "blocked", which
                     // now means hidden, read the old attribute as well.
                     final String blockedStr = parser.getAttributeValue(null, ATTR_BLOCKED);
@@ -1645,25 +1647,21 @@
                     final String hiddenStr = parser.getAttributeValue(null, ATTR_HIDDEN);
                     hidden = hiddenStr == null
                             ? hidden : Boolean.parseBoolean(hiddenStr);
-                    final String suspendedStr = parser.getAttributeValue(null, ATTR_SUSPENDED);
-                    final boolean suspended = suspendedStr == null
-                            ? false : Boolean.parseBoolean(suspendedStr);
-                    final String notLaunchedStr = parser.getAttributeValue(null, ATTR_NOT_LAUNCHED);
-                    final boolean notLaunched = stoppedStr == null
-                            ? false : Boolean.parseBoolean(notLaunchedStr);
-                    final String blockUninstallStr = parser.getAttributeValue(null,
-                            ATTR_BLOCK_UNINSTALL);
-                    final boolean blockUninstall = blockUninstallStr == null
-                            ? false : Boolean.parseBoolean(blockUninstallStr);
 
-                    final String verifStateStr =
-                            parser.getAttributeValue(null, ATTR_DOMAIN_VERIFICATON_STATE);
-                    final int verifState = (verifStateStr == null) ?
-                            PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED :
-                            Integer.parseInt(verifStateStr);
+                    final boolean suspended = XmlUtils.readBooleanAttribute(parser, ATTR_SUSPENDED,
+                            false);
+                    final boolean blockUninstall = XmlUtils.readBooleanAttribute(parser,
+                            ATTR_BLOCK_UNINSTALL, false);
+                    final int enabled = XmlUtils.readIntAttribute(parser, ATTR_ENABLED,
+                            COMPONENT_ENABLED_STATE_DEFAULT);
+                    final String enabledCaller = parser.getAttributeValue(null,
+                            ATTR_ENABLED_CALLER);
 
-                    final String linkGenStr = parser.getAttributeValue(null, ATTR_APP_LINK_GENERATION);
-                    final int linkGeneration = linkGenStr == null ? 0 : Integer.parseInt(linkGenStr);
+                    final int verifState = XmlUtils.readIntAttribute(parser,
+                            ATTR_DOMAIN_VERIFICATON_STATE,
+                            PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
+                    final int linkGeneration = XmlUtils.readIntAttribute(parser,
+                            ATTR_APP_LINK_GENERATION, 0);
                     if (linkGeneration > maxAppLinkGeneration) {
                         maxAppLinkGeneration = linkGeneration;
                     }
@@ -1687,8 +1685,8 @@
                         }
                     }
 
-                    ps.setUserState(userId, enabled, installed, stopped, notLaunched, hidden,
-                            suspended, enabledCaller, enabledComponents, disabledComponents,
+                    ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
+                            hidden, suspended, enabledCaller, enabledComponents, disabledComponents,
                             blockUninstall, verifState, linkGeneration);
                 } else if (tagName.equals("preferred-activities")) {
                     readPreferredActivitiesLPw(parser, userId);
@@ -1927,80 +1925,69 @@
             serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
 
             for (final PackageSetting pkg : mPackages.values()) {
-                PackageUserState ustate = pkg.readUserState(userId);
-                if (ustate.stopped || ustate.notLaunched || !ustate.installed
-                        || ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT
-                        || ustate.hidden
-                        || ustate.suspended
-                        || (ustate.enabledComponents != null
-                                && ustate.enabledComponents.size() > 0)
-                        || (ustate.disabledComponents != null
-                                && ustate.disabledComponents.size() > 0)
-                        || ustate.blockUninstall
-                        || (ustate.domainVerificationStatus !=
-                            PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED)) {
-                    serializer.startTag(null, TAG_PACKAGE);
-                    serializer.attribute(null, ATTR_NAME, pkg.name);
-                    if (DEBUG_MU) Log.i(TAG, "  pkg=" + pkg.name + ", state=" + ustate.enabled);
+                final PackageUserState ustate = pkg.readUserState(userId);
+                if (DEBUG_MU) Log.i(TAG, "  pkg=" + pkg.name + ", state=" + ustate.enabled);
 
-                    if (!ustate.installed) {
-                        serializer.attribute(null, ATTR_INSTALLED, "false");
-                    }
-                    if (ustate.stopped) {
-                        serializer.attribute(null, ATTR_STOPPED, "true");
-                    }
-                    if (ustate.notLaunched) {
-                        serializer.attribute(null, ATTR_NOT_LAUNCHED, "true");
-                    }
-                    if (ustate.hidden) {
-                        serializer.attribute(null, ATTR_HIDDEN, "true");
-                    }
-                    if (ustate.suspended) {
-                        serializer.attribute(null, ATTR_SUSPENDED, "true");
-                    }
-                    if (ustate.blockUninstall) {
-                        serializer.attribute(null, ATTR_BLOCK_UNINSTALL, "true");
-                    }
-                    if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
-                        serializer.attribute(null, ATTR_ENABLED,
-                                Integer.toString(ustate.enabled));
-                        if (ustate.lastDisableAppCaller != null) {
-                            serializer.attribute(null, ATTR_ENABLED_CALLER,
-                                    ustate.lastDisableAppCaller);
-                        }
-                    }
-                    if (ustate.domainVerificationStatus !=
-                            PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
-                        serializer.attribute(null, ATTR_DOMAIN_VERIFICATON_STATE,
-                                Integer.toString(ustate.domainVerificationStatus));
-                    }
-                    if (ustate.appLinkGeneration != 0) {
-                        serializer.attribute(null, ATTR_APP_LINK_GENERATION,
-                                Integer.toString(ustate.appLinkGeneration));
-                    }
-                    if (ustate.enabledComponents != null
-                            && ustate.enabledComponents.size() > 0) {
-                        serializer.startTag(null, TAG_ENABLED_COMPONENTS);
-                        for (final String name : ustate.enabledComponents) {
-                            serializer.startTag(null, TAG_ITEM);
-                            serializer.attribute(null, ATTR_NAME, name);
-                            serializer.endTag(null, TAG_ITEM);
-                        }
-                        serializer.endTag(null, TAG_ENABLED_COMPONENTS);
-                    }
-                    if (ustate.disabledComponents != null
-                            && ustate.disabledComponents.size() > 0) {
-                        serializer.startTag(null, TAG_DISABLED_COMPONENTS);
-                        for (final String name : ustate.disabledComponents) {
-                            serializer.startTag(null, TAG_ITEM);
-                            serializer.attribute(null, ATTR_NAME, name);
-                            serializer.endTag(null, TAG_ITEM);
-                        }
-                        serializer.endTag(null, TAG_DISABLED_COMPONENTS);
-                    }
-
-                    serializer.endTag(null, TAG_PACKAGE);
+                serializer.startTag(null, TAG_PACKAGE);
+                serializer.attribute(null, ATTR_NAME, pkg.name);
+                if (ustate.ceDataInode != 0) {
+                    XmlUtils.writeLongAttribute(serializer, ATTR_CE_DATA_INODE, ustate.ceDataInode);
                 }
+                if (!ustate.installed) {
+                    serializer.attribute(null, ATTR_INSTALLED, "false");
+                }
+                if (ustate.stopped) {
+                    serializer.attribute(null, ATTR_STOPPED, "true");
+                }
+                if (ustate.notLaunched) {
+                    serializer.attribute(null, ATTR_NOT_LAUNCHED, "true");
+                }
+                if (ustate.hidden) {
+                    serializer.attribute(null, ATTR_HIDDEN, "true");
+                }
+                if (ustate.suspended) {
+                    serializer.attribute(null, ATTR_SUSPENDED, "true");
+                }
+                if (ustate.blockUninstall) {
+                    serializer.attribute(null, ATTR_BLOCK_UNINSTALL, "true");
+                }
+                if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
+                    serializer.attribute(null, ATTR_ENABLED,
+                            Integer.toString(ustate.enabled));
+                    if (ustate.lastDisableAppCaller != null) {
+                        serializer.attribute(null, ATTR_ENABLED_CALLER,
+                                ustate.lastDisableAppCaller);
+                    }
+                }
+                if (ustate.domainVerificationStatus !=
+                        PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+                    XmlUtils.writeIntAttribute(serializer, ATTR_DOMAIN_VERIFICATON_STATE,
+                            ustate.domainVerificationStatus);
+                }
+                if (ustate.appLinkGeneration != 0) {
+                    XmlUtils.writeIntAttribute(serializer, ATTR_APP_LINK_GENERATION,
+                            ustate.appLinkGeneration);
+                }
+                if (!ArrayUtils.isEmpty(ustate.enabledComponents)) {
+                    serializer.startTag(null, TAG_ENABLED_COMPONENTS);
+                    for (final String name : ustate.enabledComponents) {
+                        serializer.startTag(null, TAG_ITEM);
+                        serializer.attribute(null, ATTR_NAME, name);
+                        serializer.endTag(null, TAG_ITEM);
+                    }
+                    serializer.endTag(null, TAG_ENABLED_COMPONENTS);
+                }
+                if (!ArrayUtils.isEmpty(ustate.disabledComponents)) {
+                    serializer.startTag(null, TAG_DISABLED_COMPONENTS);
+                    for (final String name : ustate.disabledComponents) {
+                        serializer.startTag(null, TAG_ITEM);
+                        serializer.attribute(null, ATTR_NAME, name);
+                        serializer.endTag(null, TAG_ITEM);
+                    }
+                    serializer.endTag(null, TAG_DISABLED_COMPONENTS);
+                }
+
+                serializer.endTag(null, TAG_PACKAGE);
             }
 
             writePreferredActivitiesLPr(serializer, userId, true);
@@ -4532,6 +4519,8 @@
 
         for (UserInfo user : users) {
             pw.print(prefix); pw.print("  User "); pw.print(user.id); pw.print(": ");
+            pw.print("ceDataInode=");
+            pw.print(ps.getCeDataInode(user.id));
             pw.print(" installed=");
             pw.print(ps.getInstalled(user.id));
             pw.print(" hidden=");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7f40079..c991130 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -642,13 +642,14 @@
      * Subtracts the insets calculated by intersecting {@param layoutFrame} with {@param insetFrame}
      * from {@param frame}. In other words, it applies the insets that would result if
      * {@param frame} would be shifted to {@param layoutFrame} and then applying the insets from
-     * {@param insetFrame}.
+     * {@param insetFrame}. Also it respects {@param displayFrame} in case window has minimum
+     * width/height applied and insets should be overridden.
      */
-    private void subtractInsets(Rect frame, Rect layoutFrame, Rect insetFrame) {
-        final int left = Math.max(0, insetFrame.left - layoutFrame.left);
-        final int top = Math.max(0, insetFrame.top - layoutFrame.top);
-        final int right = Math.max(0, layoutFrame.right - insetFrame.right);
-        final int bottom = Math.max(0, layoutFrame.bottom - insetFrame.bottom);
+    private void subtractInsets(Rect frame, Rect layoutFrame, Rect insetFrame, Rect displayFrame) {
+        final int left = Math.max(0, insetFrame.left - Math.max(layoutFrame.left, displayFrame.left));
+        final int top = Math.max(0, insetFrame.top - Math.max(layoutFrame.top, displayFrame.top));
+        final int right = Math.max(0, Math.min(layoutFrame.right, displayFrame.right) - insetFrame.right);
+        final int bottom = Math.max(0, Math.min(layoutFrame.bottom, displayFrame.bottom) - insetFrame.bottom);
         frame.inset(left, top, right, bottom);
     }
 
@@ -687,8 +688,7 @@
         // The offset from the layout containing frame to the actual containing frame.
         final int layoutXDiff;
         final int layoutYDiff;
-        if (mInsetFrame.isEmpty()  && (fullscreenTask
-                || layoutInParentFrame())) {
+        if (mInsetFrame.isEmpty() && (fullscreenTask || layoutInParentFrame())) {
             // We use the parent frame as the containing frame for fullscreen and child windows
             mContainingFrame.set(pf);
             mDisplayFrame.set(df);
@@ -733,10 +733,12 @@
             layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
             layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
             layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
-            subtractInsets(mDisplayFrame, layoutContainingFrame, df);
+            mTmpRect.set(0, 0, mDisplayContent.getDisplayInfo().logicalWidth,
+                    mDisplayContent.getDisplayInfo().logicalHeight);
+            subtractInsets(mDisplayFrame, layoutContainingFrame, df, mTmpRect);
             if (!layoutInParentFrame()) {
-                subtractInsets(mContainingFrame, layoutContainingFrame, pf);
-                subtractInsets(mInsetFrame, layoutContainingFrame, pf);
+                subtractInsets(mContainingFrame, layoutContainingFrame, pf, mTmpRect);
+                subtractInsets(mInsetFrame, layoutContainingFrame, pf, mTmpRect);
             }
             layoutDisplayFrame = df;
             layoutDisplayFrame.intersect(layoutContainingFrame);
@@ -855,8 +857,8 @@
             getDisplayContent().getLogicalDisplayRect(mTmpRect);
             // Override right and/or bottom insets in case if the frame doesn't fit the screen in
             // non-fullscreen mode.
-            boolean overrideRightInset = !fullscreenTask && mFrame.right > mTmpRect.right;
-            boolean overrideBottomInset = !fullscreenTask && mFrame.bottom > mTmpRect.bottom;
+            boolean overrideRightInset = !fullscreenTask && layoutContainingFrame.right > mTmpRect.right;
+            boolean overrideBottomInset = !fullscreenTask && layoutContainingFrame.bottom > mTmpRect.bottom;
             mContentInsets.set(mContentFrame.left - layoutContainingFrame.left,
                     mContentFrame.top - layoutContainingFrame.top,
                     overrideRightInset ? mTmpRect.right - mContentFrame.right
@@ -2590,7 +2592,7 @@
         final int ph = containingFrame.height();
         final Task task = getTask();
         final boolean nonFullscreenTask = isInMultiWindowMode();
-        final boolean fitToDisplay = task != null && !task.isFloating() && !layoutInParentFrame();
+        final boolean fitToDisplay = task != null && !nonFullscreenTask && !layoutInParentFrame();
         float x, y;
         int w,h;