Merge "Register functor draw correctly"
diff --git a/Android.mk b/Android.mk
index e324a75..749242d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -326,6 +326,7 @@
 	core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
 	core/java/android/view/autofill/IAutoFillManager.aidl \
 	core/java/android/view/autofill/IAutoFillManagerClient.aidl \
+	core/java/android/view/autofill/IAutofillWindowPresenter.aidl \
 	core/java/android/view/IApplicationToken.aidl \
 	core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
 	core/java/android/view/IDockedStackListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index f8c1c6a..8f969c1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -879,6 +879,7 @@
     field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
     field public static final int matchOrder = 16843855; // 0x101044f
     field public static final int max = 16843062; // 0x1010136
+    field public static final int maxAspectRatio = 16844131; // 0x1010563
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
diff --git a/api/removed.txt b/api/removed.txt
index 8acf4ad..75da976 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -380,16 +380,6 @@
 
 }
 
-package android.view.textclassifier {
-
-  public abstract interface TextClassifier {
-    method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
-    method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
-    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
-}
-
 package android.webkit {
 
   public class WebViewClient {
diff --git a/api/system-current.txt b/api/system-current.txt
index 65588fb..48b878e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -992,6 +992,7 @@
     field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
     field public static final int matchOrder = 16843855; // 0x101044f
     field public static final int max = 16843062; // 0x1010136
+    field public static final int maxAspectRatio = 16844131; // 0x1010563
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
diff --git a/api/system-removed.txt b/api/system-removed.txt
index a2fcbcd..3aa9398 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -374,16 +374,6 @@
 
 }
 
-package android.view.textclassifier {
-
-  public abstract interface TextClassifier {
-    method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
-    method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
-    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
-}
-
 package android.webkit {
 
   public class WebViewClient {
diff --git a/api/test-current.txt b/api/test-current.txt
index 8262223..4d8d7f2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -879,6 +879,7 @@
     field public static final int marqueeRepeatLimit = 16843293; // 0x101021d
     field public static final int matchOrder = 16843855; // 0x101044f
     field public static final int max = 16843062; // 0x1010136
+    field public static final int maxAspectRatio = 16844131; // 0x1010563
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 8acf4ad..75da976 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -380,16 +380,6 @@
 
 }
 
-package android.view.textclassifier {
-
-  public abstract interface TextClassifier {
-    method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
-    method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
-    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
-}
-
 package android.webkit {
 
   public class WebViewClient {
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index ffc0f87..db17b28 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -53,15 +53,15 @@
 
         String arg = nextArg();
         if (arg.equals("backup")) {
-            doFullBackup(OsConstants.STDOUT_FILENO);
+            doBackup(OsConstants.STDOUT_FILENO);
         } else if (arg.equals("restore")) {
-            doFullRestore(OsConstants.STDIN_FILENO);
+            doRestore(OsConstants.STDIN_FILENO);
         } else {
             Log.e(TAG, "Invalid operation '" + arg + "'");
         }
     }
 
-    private void doFullBackup(int socketFd) {
+    private void doBackup(int socketFd) {
         ArrayList<String> packages = new ArrayList<String>();
         boolean saveApks = false;
         boolean saveObbs = false;
@@ -70,6 +70,7 @@
         boolean doWidgets = false;
         boolean allIncludesSystem = true;
         boolean doCompress = true;
+        boolean doKeyValue = false;
 
         String arg;
         while ((arg = nextArg()) != null) {
@@ -100,6 +101,8 @@
                     doCompress = true;
                 } else if ("-nocompress".equals(arg)) {
                     doCompress = false;
+                } else if ("-includekeyvalue".equals(arg)) {
+                    doKeyValue = true;
                 } else {
                     Log.w(TAG, "Unknown backup flag " + arg);
                     continue;
@@ -123,8 +126,8 @@
         try {
             fd = ParcelFileDescriptor.adoptFd(socketFd);
             String[] packArray = new String[packages.size()];
-            mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets,
-                    doEverything, allIncludesSystem, doCompress, packages.toArray(packArray));
+            mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
+                    allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray));
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to invoke backup manager for backup");
         } finally {
@@ -136,12 +139,12 @@
         }
     }
 
-    private void doFullRestore(int socketFd) {
+    private void doRestore(int socketFd) {
         // No arguments to restore
         ParcelFileDescriptor fd = null;
         try {
             fd = ParcelFileDescriptor.adoptFd(socketFd);
-            mBackupManager.fullRestore(fd);
+            mBackupManager.adbRestore(fd);
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to invoke backup manager for restore");
         } finally {
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index db3772d..658d662 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -94,6 +94,8 @@
             runGetFbeMode();
         } else if ("fstrim".equals(op)) {
             runFstrim();
+        } else if ("set-virtual-disk".equals(op)) {
+            runSetVirtualDisk();
         } else {
             throw new IllegalArgumentException();
         }
@@ -225,6 +227,12 @@
         mSm.fstrim(0);
     }
 
+    public void runSetVirtualDisk() throws RemoteException {
+        final boolean virtualDisk = Boolean.parseBoolean(nextArg());
+        mSm.setDebugFlags(virtualDisk ? StorageManager.DEBUG_VIRTUAL_DISK : 0,
+                StorageManager.DEBUG_VIRTUAL_DISK);
+    }
+
     private String nextArg() {
         if (mNextArg >= mArgs.length) {
             return null;
@@ -240,6 +248,7 @@
         System.err.println("       sm has-adoptable");
         System.err.println("       sm get-primary-storage-uuid");
         System.err.println("       sm set-force-adoptable [true|false]");
+        System.err.println("       sm set-virtual-disk [true|false]");
         System.err.println("");
         System.err.println("       sm partition DISK [public|private|mixed] [ratio]");
         System.err.println("       sm mount VOLUME");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4fd3f39..fa1de03 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,9 +17,12 @@
 package android.app;
 
 import android.metrics.LogMaker;
+import android.graphics.Rect;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillPopupWindow;
 import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ToolbarActionBar;
@@ -768,7 +771,6 @@
     /*package*/ Configuration mCurrentConfig;
     private SearchManager mSearchManager;
     private MenuInflater mMenuInflater;
-    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     static final class NonConfigurationInstances {
         Object activity;
@@ -850,6 +852,8 @@
 
     private boolean mAutoFillResetNeeded;
 
+    private AutofillPopupWindow mAutofillPopupWindow;
+
     private static native String getDlWarning();
 
     /** Return the intent that started this activity. */
@@ -7190,60 +7194,7 @@
 
     /** @hide */
     @Override
-    public void autofill(List<AutofillId> ids, List<AutofillValue> values) {
-        final View root = getWindow().getDecorView();
-        final int itemCount = ids.size();
-        int numApplied = 0;
-        ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
-
-        for (int i = 0; i < itemCount; i++) {
-            final AutofillId id = ids.get(i);
-            final AutofillValue value = values.get(i);
-            final int viewId = id.getViewId();
-            final View view = root.findViewByAccessibilityIdTraversal(viewId);
-            if (view == null) {
-                Log.w(TAG, "autofill(): no View with id " + viewId);
-                continue;
-            }
-            if (id.isVirtual()) {
-                final int parentId = id.getViewId();
-                if (virtualValues == null) {
-                    // Most likely there will be just one view with virtual children.
-                    virtualValues = new ArrayMap<>(1);
-                }
-                SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
-                if (valuesByParent == null) {
-                    // We don't know the size yet, but usually it will be just a few fields...
-                    valuesByParent = new SparseArray<>(5);
-                    virtualValues.put(view, valuesByParent);
-                }
-                valuesByParent.put(id.getVirtualChildId(), value);
-            } else {
-                if (view.autofill(value)) {
-                    numApplied++;
-                }
-            }
-        }
-
-        if (virtualValues != null) {
-            for (int i = 0; i < virtualValues.size(); i++) {
-                final View parent = virtualValues.keyAt(i);
-                final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
-                if (parent.autofill(childrenValues)) {
-                    numApplied += childrenValues.size();
-                }
-            }
-        }
-
-        final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
-        log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
-        log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
-        mMetricsLogger.write(log);
-    }
-
-    /** @hide */
-    @Override
-    public void authenticate(IntentSender intent, Intent fillInIntent) {
+    final public void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent) {
         try {
             startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX,
                     0, fillInIntent, 0, 0, null);
@@ -7254,10 +7205,47 @@
 
     /** @hide */
     @Override
-    public void resetableStateAvailable() {
+    final public void autofillCallbackResetableStateAvailable() {
         mAutoFillResetNeeded = true;
     }
 
+    /** @hide */
+    @Override
+    final public boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width,
+            int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) {
+        final Rect actualAnchorBounds = new Rect();
+        anchor.getBoundsOnScreen(actualAnchorBounds);
+
+        final int offsetX = (anchorBounds != null)
+                ? anchorBounds.left - actualAnchorBounds.left : 0;
+        int offsetY = (anchorBounds != null)
+                ? anchorBounds.top - actualAnchorBounds.top : 0;
+
+        final boolean wasShowing;
+
+        if (mAutofillPopupWindow == null) {
+            wasShowing = false;
+            mAutofillPopupWindow = new AutofillPopupWindow(presenter);
+        } else {
+            wasShowing = mAutofillPopupWindow.isShowing();
+        }
+        mAutofillPopupWindow.update(anchor, offsetX, offsetY, width, height, anchorBounds,
+                actualAnchorBounds);
+
+        return !wasShowing && mAutofillPopupWindow.isShowing();
+    }
+
+    /** @hide */
+    @Override
+    final public boolean autofillCallbackRequestHideFillUi() {
+        if (mAutofillPopupWindow == null) {
+            return false;
+        }
+        mAutofillPopupWindow.dismiss();
+        mAutofillPopupWindow = null;
+        return true;
+    }
+
     /**
      * If set to true, this indicates to the system that it should never take a
      * screenshot of the activity to be used as a representation while it is not in a started state.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index b9c888c..4c080c9 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -367,6 +367,7 @@
         // STOPSHIP: fix buggy apps
         if (SystemProperties.getBoolean("fw.ignore_buggy", false)) return false;
         if ("com.google.android.tts".equals(getApplicationInfo().packageName)) return true;
+        if ("com.breel.geswallpapers".equals(getApplicationInfo().packageName)) return true;
         return false;
     }
 
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 7f0b6fb..cae54b6 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -53,21 +53,6 @@
     int getNightMode();
 
     /**
-     * Sets whith theme overlays to use within /vendor/overlay.
-     */
-    void setTheme(String theme);
-
-    /**
-     * Gets which theme overlays to use within /vendor/overlay.
-     */
-    String getTheme();
-
-    /**
-     * Gets the themes available in /vendor/overlay.
-     */
-    String[] getAvailableThemes();
-
-    /**
      * Tells if UI mode is locked or not.
      */
     boolean isUiModeLocked();
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index af41a7d..07e2570 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -242,50 +242,6 @@
     }
 
     /**
-     * Sets the vendor theme overlay property, then triggers a reboot.
-     * @hide
-     */
-    public void setTheme(String theme) {
-        if (mService != null) {
-            try {
-                mService.setTheme(theme);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * Gets the vendor theme overlay property.
-     * @hide
-     */
-    public String getTheme() {
-        if (mService != null) {
-            try {
-                return mService.getTheme();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets the valid inputs to {@link #setTheme(String)}.
-     * @hide
-     */
-    public String[] getAvailableThemes() {
-        if (mService != null) {
-            try {
-                return mService.getAvailableThemes();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return null;
-    }
-
-    /**
      * Returns the currently configured night mode.
      * <p>
      * May be one of:
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 391885d..6d8d5e9 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7207,7 +7207,7 @@
      * @return {@code true} if security logging is enabled by device owner, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not a device owner.
      */
-    public boolean isSecurityLoggingEnabled(@NonNull ComponentName admin) {
+    public boolean isSecurityLoggingEnabled(@Nullable ComponentName admin) {
         throwIfParentInstance("isSecurityLoggingEnabled");
         try {
             return mService.isSecurityLoggingEnabled(admin);
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 76828ee..a5dd5bd 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -56,6 +56,7 @@
 
     public static final String APK_TREE_TOKEN = "a";
     public static final String OBB_TREE_TOKEN = "obb";
+    public static final String KEY_VALUE_DATA_TOKEN = "k";
 
     public static final String ROOT_TREE_TOKEN = "r";
     public static final String FILES_TREE_TOKEN = "f";
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 59a941a..9c3b110 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -144,9 +144,10 @@
     void backupNow();
 
     /**
-     * Write a full backup of the given package to the supplied file descriptor.
+     * Write a backup of the given package to the supplied file descriptor.
      * The fd may be a socket or other non-seekable destination.  If no package names
      * are supplied, then every application on the device will be backed up to the output.
+     * Currently only used by the 'adb backup' command.
      *
      * <p>This method is <i>synchronous</i> -- it does not return until the backup has
      * completed.
@@ -167,12 +168,14 @@
      *     as including packages pre-installed as part of the system. If {@code false},
      *     then setting {@code allApps} to {@code true} will mean only that all 3rd-party
      *     applications will be included in the dataset.
+     * @param doKeyValue If {@code true}, also packages supporting key-value backup will be backed
+     *     up. If {@code false}, key-value packages will be skipped.
      * @param packageNames The package names of the apps whose data (and optionally .apk files)
      *     are to be backed up.  The <code>allApps</code> parameter supersedes this.
      */
-    void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+    void adbBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
             boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
-            boolean doCompress, in String[] packageNames);
+            boolean doCompress, boolean doKeyValue, in String[] packageNames);
 
     /**
      * Perform a full-dataset backup of the given applications via the currently active
@@ -184,11 +187,12 @@
 
     /**
      * Restore device content from the data stream passed through the given socket.  The
-     * data stream must be in the format emitted by fullBackup().
+     * data stream must be in the format emitted by adbBackup().
+     * Currently only used by the 'adb restore' command.
      *
      * <p>Callers must hold the android.permission.BACKUP permission to use this method.
      */
-    void fullRestore(in ParcelFileDescriptor fd);
+    void adbRestore(in ParcelFileDescriptor fd);
 
     /**
      * Confirm that the requested full backup/restore operation can proceed.  The system will
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 6b3ef4a..9e2eb84 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -141,6 +141,7 @@
              * Application interface registered - app is ready to go
              * @hide
              */
+            @Override
             public void onClientRegistered(int status, int clientIf) {
                 if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status
                     + " clientIf=" + clientIf);
@@ -210,6 +211,7 @@
              * Client connection state changed
              * @hide
              */
+            @Override
             public void onClientConnectionState(int status, int clientIf,
                                                 boolean connected, String address) {
                 if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
@@ -245,6 +247,7 @@
              * we are done at this point.
              * @hide
              */
+            @Override
             public void onSearchComplete(String address, List<BluetoothGattService> services,
                                          int status) {
                 if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
@@ -288,6 +291,7 @@
              * Updates the internal value.
              * @hide
              */
+            @Override
             public void onCharacteristicRead(String address, int status, int handle, byte[] value) {
                 if (VDBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
                             + " handle=" + handle + " Status=" + status);
@@ -336,6 +340,7 @@
              * Let the app know how we did...
              * @hide
              */
+            @Override
             public void onCharacteristicWrite(String address, int status, int handle) {
                 if (VDBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
                             + " handle=" + handle + " Status=" + status);
@@ -380,6 +385,7 @@
              * Updates the internal value.
              * @hide
              */
+            @Override
             public void onNotify(String address, int handle, byte[] value) {
                 if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle);
 
@@ -403,6 +409,7 @@
              * Descriptor has been read.
              * @hide
              */
+            @Override
             public void onDescriptorRead(String address, int status, int handle, byte[] value) {
                 if (VDBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " handle=" + handle);
 
@@ -446,6 +453,7 @@
              * Descriptor write operation complete.
              * @hide
              */
+            @Override
             public void onDescriptorWrite(String address, int status, int handle) {
                 if (VDBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " handle=" + handle);
 
@@ -488,6 +496,7 @@
              * Prepared write transaction completed (or aborted)
              * @hide
              */
+            @Override
             public void onExecuteWrite(String address, int status) {
                 if (VDBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
                     + " status=" + status);
@@ -510,6 +519,7 @@
              * Remote device RSSI has been read
              * @hide
              */
+            @Override
             public void onReadRemoteRssi(String address, int rssi, int status) {
                 if (VDBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
                             " rssi=" + rssi + " status=" + status);
@@ -527,6 +537,7 @@
              * Callback invoked when the MTU for a given connection changes
              * @hide
              */
+            @Override
             public void onConfigureMTU(String address, int mtu, int status) {
                 if (DBG) Log.d(TAG, "onConfigureMTU() - Device=" + address +
                             " mtu=" + mtu + " status=" + status);
@@ -539,6 +550,27 @@
                     Log.w(TAG, "Unhandled exception in callback", ex);
                 }
             }
+
+            /**
+             * Callback invoked when the given connection is updated
+             * @hide
+             */
+            @Override
+            public void onConnectionUpdated(String address, int interval, int latency,
+                                            int timeout, int status) {
+                if (DBG) Log.d(TAG, "onConnectionUpdated() - Device=" + address +
+                            " interval=" + interval + " latency=" + latency +
+                            " timeout=" + timeout + " status=" + status);
+                if (!address.equals(mDevice.getAddress())) {
+                    return;
+                }
+                try {
+                    mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
+                                                  timeout, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception in callback", ex);
+                }
+            }
         };
 
     /*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
index be69df9..11a15c6 100644
--- a/core/java/android/bluetooth/BluetoothGattCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -179,4 +179,22 @@
      */
     public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
     }
+
+    /**
+     * Callback indicating the connection parameters were updated.
+     *
+     * @param gatt GATT client involved
+     * @param interval Connection interval used on this connection, 1.25ms unit. Valid
+     *            range is from 6 (7.5ms) to 3200 (4000ms).
+     * @param latency Slave latency for the connection in number of connection events. Valid
+     *            range is from 0 to 499
+     * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is
+     *            from 10 (0.1s) to 3200 (32s)
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated
+     *                successfully
+     * @hide
+     */
+    public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout,
+                                    int status) {
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 1bd7bd4..c991e2f 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -65,6 +65,7 @@
              * Application interface registered - app is ready to go
              * @hide
              */
+            @Override
             public void onServerRegistered(int status, int serverIf) {
                 if (DBG) Log.d(TAG, "onServerRegistered() - status=" + status
                     + " serverIf=" + serverIf);
@@ -80,18 +81,10 @@
             }
 
             /**
-             * Callback reporting an LE scan result.
-             * @hide
-             */
-            public void onScanResult(String address, int rssi, byte[] advData) {
-                if (VDBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
-                // no op
-            }
-
-            /**
              * Server connection state changed
              * @hide
              */
+            @Override
             public void onServerConnectionState(int status, int serverIf,
                                                 boolean connected, String address) {
                 if (DBG) Log.d(TAG, "onServerConnectionState() - status=" + status
@@ -109,6 +102,7 @@
              * Service has been added
              * @hide
              */
+            @Override
             public void onServiceAdded(int status, BluetoothGattService service) {
                 if (DBG) Log.d(TAG, "onServiceAdded() - handle=" + service.getInstanceId()
                     + " uuid=" + service.getUuid() + " status=" + status);
@@ -149,6 +143,7 @@
              * Remote client characteristic read request.
              * @hide
              */
+            @Override
             public void onCharacteristicReadRequest(String address, int transId,
                             int offset, boolean isLong, int handle) {
                 if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
@@ -171,6 +166,7 @@
              * Remote client descriptor read request.
              * @hide
              */
+            @Override
             public void onDescriptorReadRequest(String address, int transId,
                             int offset, boolean isLong, int handle) {
                 if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
@@ -193,6 +189,7 @@
              * Remote client characteristic write request.
              * @hide
              */
+            @Override
             public void onCharacteristicWriteRequest(String address, int transId,
                             int offset, int length, boolean isPrep, boolean needRsp,
                             int handle, byte[] value) {
@@ -218,6 +215,7 @@
              * Remote client descriptor write request.
              * @hide
              */
+            @Override
             public void onDescriptorWriteRequest(String address, int transId, int offset,
                             int length, boolean isPrep, boolean needRsp, int handle, byte[] value) {
                 if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - handle=" + handle);
@@ -241,6 +239,7 @@
              * Execute pending writes.
              * @hide
              */
+            @Override
             public void onExecuteWrite(String address, int transId,
                                        boolean execWrite) {
                 if (DBG) Log.d(TAG, "onExecuteWrite() - "
@@ -261,6 +260,7 @@
              * A notification/indication has been sent.
              * @hide
              */
+            @Override
             public void onNotificationSent(String address, int status) {
                 if (VDBG) Log.d(TAG, "onNotificationSent() - "
                     + "device=" + address + ", status=" + status);
@@ -279,6 +279,7 @@
              * The MTU for a connection has changed
              * @hide
              */
+            @Override
             public void onMtuChanged(String address, int mtu) {
                 if (DBG) Log.d(TAG, "onMtuChanged() - "
                     + "device=" + address + ", mtu=" + mtu);
@@ -297,6 +298,7 @@
              * The PHY for a connection was updated
              * @hide
              */
+            @Override
             public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
                 if (DBG) Log.d(TAG, "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
                     + ", rxPHy=" + rxPhy);
@@ -315,6 +317,7 @@
              * The PHY for a connection was read
              * @hide
              */
+            @Override
             public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
                 if (DBG) Log.d(TAG, "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
                     + ", rxPHy=" + rxPhy);
@@ -328,6 +331,28 @@
                     Log.w(TAG, "Unhandled exception: " + ex);
                 }
             }
+
+            /**
+             * Callback invoked when the given connection is updated
+             * @hide
+             */
+            @Override
+            public void onConnectionUpdated(String address, int interval, int latency,
+                                            int timeout, int status) {
+                if (DBG) Log.d(TAG, "onConnectionUpdated() - Device=" + address +
+                            " interval=" + interval + " latency=" + latency +
+                            " timeout=" + timeout + " status=" + status);
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                if (device == null) return;
+
+                try {
+                    mCallback.onConnectionUpdated(device, interval, latency,
+                                                  timeout, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
         };
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index 0a89072..3b8f962 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -184,4 +184,23 @@
      */
     public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
     }
+
+    /**
+     * Callback indicating the connection parameters were updated.
+     *
+     * @param device The remote device involved
+     * @param interval Connection interval used on this connection, 1.25ms unit. Valid
+     *            range is from 6 (7.5ms) to 3200 (4000ms).
+     * @param latency Slave latency for the connection in number of connection events. Valid
+     *            range is from 0 to 499
+     * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is
+     *            from 10 (0.1s) to 3200 (32s)
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated
+     *                successfully
+     * @hide
+     */
+    public void onConnectionUpdated(BluetoothDevice gatt, int interval, int latency, int timeout,
+                                    int status) {
+    }
+
 }
diff --git a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
index 736f4b2..ed69e54 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
@@ -37,4 +37,6 @@
     void onNotify(in String address, in int handle, in byte[] value);
     void onReadRemoteRssi(in String address, in int rssi, in int status);
     void onConfigureMTU(in String address, in int mtu, in int status);
+    void onConnectionUpdated(in String address, in int interval, in int latency,
+                             in int timeout, in int status);
 }
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
index 091ffb3..267e882 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
@@ -42,4 +42,6 @@
     void onMtuChanged(in String address, in int mtu);
     void onPhyUpdate(in String address, in int txPhy, in int rxPhy, in int status);
     void onPhyRead(in String address, in int txPhy, in int rxPhy, in int status);
+    void onConnectionUpdated(in String address, in int interval, in int latency,
+                             in int timeout, in int status);
 }
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index ecdc0ce..78e3de4 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -214,7 +214,7 @@
     }
 
     private boolean checkFeaturePresent() {
-        boolean featurePresent = mService == null;
+        boolean featurePresent = mService != null;
         if (!featurePresent && DEBUG) {
             Log.d(LOG_TAG, "Feature " + PackageManager.FEATURE_COMPANION_DEVICE_SETUP
                     + " not available");
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index c28172c..f1c2f34 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -150,7 +150,7 @@
 
     public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
         synchronized (mPrimaryClipChangedListeners) {
-            if (mPrimaryClipChangedListeners.size() == 0) {
+            if (mPrimaryClipChangedListeners.isEmpty()) {
                 try {
                     mService.addPrimaryClipChangedListener(
                             mPrimaryClipChangedServiceListener, mContext.getOpPackageName());
@@ -165,7 +165,7 @@
     public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
         synchronized (mPrimaryClipChangedListeners) {
             mPrimaryClipChangedListeners.remove(what);
-            if (mPrimaryClipChangedListeners.size() == 0) {
+            if (mPrimaryClipChangedListeners.isEmpty()) {
                 try {
                     mService.removePrimaryClipChangedListener(
                             mPrimaryClipChangedServiceListener);
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index fd1e24a..8f3a317 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -508,14 +508,14 @@
         /** Create a ContentProviderOperation from this {@link Builder}. */
         public ContentProviderOperation build() {
             if (mType == TYPE_UPDATE) {
-                if ((mValues == null || mValues.size() == 0)
-                        && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)) {
+                if ((mValues == null || mValues.isEmpty())
+                      && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())) {
                     throw new IllegalArgumentException("Empty values");
                 }
             }
             if (mType == TYPE_ASSERT) {
-                if ((mValues == null || mValues.size() == 0)
-                        && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)
+                if ((mValues == null || mValues.isEmpty())
+                      && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())
                         && (mExpectedCount == null)) {
                     throw new IllegalArgumentException("Empty values");
                 }
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 3a87cb3..6f93798 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -204,6 +204,17 @@
     }
 
     /**
+     * Indicates whether this collection is empty.
+     *
+     * @return true iff size == 0
+     * {@hide}
+     * TODO: consider exposing this new method publicly
+     */
+    public boolean isEmpty() {
+        return mValues.isEmpty();
+    }
+
+    /**
      * Remove a single value.
      *
      * @param key the name of the value to remove
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 6dbe5fb..b01e6a1 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -223,6 +223,15 @@
     public int resizeMode = RESIZE_MODE_RESIZEABLE;
 
     /**
+     * Value indicating the maximum aspect ratio the activity supports.
+     * <p>
+     * 0 means unset.
+     * @See {@link android.R.attr#maxAspectRatio}.
+     * @hide
+     */
+    public float maxAspectRatio;
+
+    /**
      * Name of the VrListenerService component to run for this activity.
      * @see android.R.attr#enableVrMode
      * @hide
@@ -922,6 +931,7 @@
         requestedVrComponent = orig.requestedVrComponent;
         rotationAnimation = orig.rotationAnimation;
         colorMode = orig.colorMode;
+        maxAspectRatio = orig.maxAspectRatio;
     }
 
     /**
@@ -1064,6 +1074,9 @@
         if (requestedVrComponent != null) {
             pw.println(prefix + "requestedVrComponent=" + requestedVrComponent);
         }
+        if (maxAspectRatio != 0) {
+            pw.println(prefix + "maxAspectRatio=" + maxAspectRatio);
+        }
         super.dumpBack(pw, prefix, flags);
     }
 
@@ -1110,6 +1123,7 @@
         dest.writeString(requestedVrComponent);
         dest.writeInt(rotationAnimation);
         dest.writeInt(colorMode);
+        dest.writeFloat(maxAspectRatio);
     }
 
     public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -1146,6 +1160,7 @@
         requestedVrComponent = source.readString();
         rotationAnimation = source.readInt();
         colorMode = source.readInt();
+        maxAspectRatio = source.readFloat();
     }
 
     /**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index ffc7719..939e20f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -603,6 +603,15 @@
     public int largestWidthLimitDp = 0;
 
     /**
+     * Value indicating the maximum aspect ratio the application supports.
+     * <p>
+     * 0 means unset.
+     * @See {@link android.R.attr#maxAspectRatio}.
+     * @hide
+     */
+    public float maxAspectRatio;
+
+    /**
      * UUID of the storage volume on which this application is being hosted. For
      * apps hosted on the default internal storage at
      * {@link Environment#getDataDirectory()}, the UUID value is {@code null}.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index fb69986..1fafe65 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -37,6 +37,7 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+import static android.os.Build.VERSION_CODES.O;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 
@@ -147,6 +148,8 @@
     public static final int APK_SIGNING_V1 = 1;
     public static final int APK_SIGNING_V2 = 2;
 
+    private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
+
     // TODO: switch outError users to PackageParserException
     // TODO: refactor "codePath" to "apkPath"
 
@@ -3465,6 +3468,8 @@
             ai.privateFlags |= PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION;
         }
 
+        ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0);
+
         ai.networkSecurityConfigRes = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestApplication_networkSecurityConfig,
                 0);
@@ -4161,6 +4166,8 @@
                 a.info.flags |= FLAG_ALWAYS_FOCUSABLE;
             }
 
+            setActivityMaxAspectRatio(a.info, sa, owner);
+
             a.info.lockTaskLaunchMode =
                     sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0);
 
@@ -4376,6 +4383,27 @@
         }
     }
 
+    private void setActivityMaxAspectRatio(ActivityInfo aInfo, TypedArray sa, Package owner) {
+        if (aInfo.resizeMode == RESIZE_MODE_RESIZEABLE
+                || aInfo.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) {
+            // Resizeable activities can be put in any aspect ratio.
+            aInfo.maxAspectRatio = 0;
+            return;
+        }
+
+        // Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater.
+        // NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD.
+        float defaultMaxAspectRatio = owner.applicationInfo.targetSdkVersion < O
+                ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
+        if (owner.applicationInfo.maxAspectRatio != 0 ) {
+            // Use the application max aspect ration as default if set.
+            defaultMaxAspectRatio = owner.applicationInfo.maxAspectRatio;
+        }
+
+        aInfo.maxAspectRatio = Math.max(1.0f, sa.getFloat(
+                R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio));
+    }
+
     /**
      * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
      * @param restartOnConfigChanges The bit mask restartOnConfigChanges fetched from
@@ -4512,6 +4540,7 @@
         info.maxRecents = target.info.maxRecents;
         info.windowLayout = target.info.windowLayout;
         info.resizeMode = target.info.resizeMode;
+        info.maxAspectRatio = target.info.maxAspectRatio;
         info.encryptionAware = info.directBootAware = target.info.directBootAware;
 
         Activity a = new Activity(mParseActivityAliasArgs, info);
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 8e17832..fe849b8 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1449,7 +1449,7 @@
             sql.append('(');
 
             Object[] bindArgs = null;
-            int size = (initialValues != null && initialValues.size() > 0)
+            int size = (initialValues != null && !initialValues.isEmpty())
                     ? initialValues.size() : 0;
             if (size > 0) {
                 bindArgs = new Object[size];
@@ -1541,7 +1541,7 @@
      */
     public int updateWithOnConflict(String table, ContentValues values,
             String whereClause, String[] whereArgs, int conflictAlgorithm) {
-        if (values == null || values.size() == 0) {
+        if (values == null || values.isEmpty()) {
             throw new IllegalArgumentException("Empty values");
         }
 
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
index f82c9e2..ad20ce5 100644
--- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
@@ -130,9 +130,11 @@
                     continue;
                 }
 
-                mKeyphrasePackageMap.put(
-                        getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors),
-                        ai.packageName);
+                KeyphraseMetadata metadata =
+                        getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors);
+                if (metadata != null) {
+                    mKeyphrasePackageMap.put(metadata, ai.packageName);
+                }
             } catch (PackageManager.NameNotFoundException e) {
                 String error = "error parsing voice enrollment meta-data for "
                         + ri.activityInfo.packageName;
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 60b27b4..0448221 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -16,6 +16,7 @@
 package android.metrics;
 
 import android.annotation.SystemApi;
+import android.content.ComponentName;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -118,6 +119,16 @@
         return this;
     }
 
+    /**
+     * @param component to replace the existing setting.
+     * @hide
+     */
+    public LogMaker setComponentName(ComponentName component) {
+        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, component.getPackageName());
+        entries.put(MetricsEvent.FIELD_CLASS_NAME, component.getClassName());
+        return this;
+    }
+
     /** Remove the package name property. */
     public LogMaker clearPackageName() {
         entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 29884b1..dd11f68 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -24,14 +24,12 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Predicate;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.telephony.SignalStrength;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
-import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.MutableBoolean;
 import android.util.Pair;
@@ -184,7 +182,7 @@
      * New in version 19:
      *   - Wakelock data (wl) gets current and max times.
      * New in version 20:
-     *   - Sensor gets a background counter.
+     *   - Sensor, BluetoothScan, WifiScan get background timers and counter.
      */
     static final String CHECKIN_VERSION = "20";
 
@@ -363,7 +361,7 @@
 
         /**
          * Returns the max duration if it is being tracked.
-         * Not all Timer subclasses track the max duration and the current duration.
+         * Not all Timer subclasses track the max, total, current durations.
 
          */
         public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
@@ -372,13 +370,28 @@
 
         /**
          * Returns the current time the timer has been active, if it is being tracked.
-         * Not all Timer subclasses track the max duration and the current duration.
+         * Not all Timer subclasses track the max, total, current durations.
          */
         public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
         }
 
         /**
+         * Returns the current time the timer has been active, if it is being tracked.
+         *
+         * Returns the total cumulative duration (i.e. sum of past durations) that this timer has
+         * been on since reset.
+         * This may differ from getTotalTimeLocked(elapsedRealtimeUs, STATS_SINCE_CHARGED)/1000 since,
+         * depending on the Timer, getTotalTimeLocked may represent the total 'blamed' or 'pooled'
+         * time, rather than the actual time. By contrast, getTotalDurationMsLocked always gives
+         * the actual total time.
+         * Not all Timer subclasses track the max, total, current durations.
+         */
+        public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
+            return -1;
+        }
+
+        /**
          * Returns whether the timer is currently running.  Some types of timers
          * (e.g. BatchTimers) don't know whether the event is currently active,
          * and report false.
@@ -477,6 +490,9 @@
         public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
         public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
         public abstract int getWifiScanCount(int which);
+        public abstract int getWifiScanBackgroundCount(int which);
+        public abstract long getWifiScanActualTime(long elapsedRealtimeUs);
+        public abstract long getWifiScanBackgroundTime(long elapsedRealtimeUs);
         public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
         public abstract int getWifiBatchedScanCount(int csphBin, int which);
         public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
@@ -486,6 +502,7 @@
         public abstract Timer getCameraTurnedOnTimer();
         public abstract Timer getForegroundActivityTimer();
         public abstract Timer getBluetoothScanTimer();
+        public abstract Timer getBluetoothScanBackgroundTimer();
 
         // Note: the following times are disjoint.  They can be added together to find the
         // total time a uid has had any processes running at all.
@@ -609,8 +626,8 @@
 
             public abstract Timer getSensorTime();
 
-            /** Returns a counter for usage count when in the background. */
-            public abstract Counter getSensorBgCount();
+            /** Returns a Timer for sensor usage when app is in the background. */
+            public abstract Timer getSensorBackgroundTime();
         }
 
         public class Pid {
@@ -2652,7 +2669,7 @@
      * @param pw a PrintWriter object to print to.
      * @param sb a StringBuilder object.
      * @param timer a Timer object contining the wakelock times.
-     * @param rawRealtime the current on-battery time in microseconds.
+     * @param rawRealtimeUs the current on-battery time in microseconds.
      * @param which which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
      * @param prefix a String to be prepended to each line of output.
      * @param type the name of the timer.
@@ -3284,19 +3301,41 @@
             final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
             final long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
             final int wifiScanCount = u.getWifiScanCount(which);
+            final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
+            // Note that 'ActualTime' are unpooled and always since reset (regardless of 'which')
+            final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime);
+            final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime);
             final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
             if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
+                    || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0
                     || uidWifiRunningTime != 0) {
                 dumpLine(pw, uid, category, WIFI_DATA, fullWifiLockOnTime, wifiScanTime,
                         uidWifiRunningTime, wifiScanCount,
-                        /* legacy fields follow, keep at 0 */ 0, 0, 0);
+                        /* legacy fields follow, keep at 0 */ 0, 0, 0,
+                        wifiScanCountBg, wifiScanActualTime, wifiScanActualTimeBg);
             }
 
             dumpControllerActivityLine(pw, uid, category, WIFI_CONTROLLER_DATA,
                     u.getWifiControllerActivity(), which);
 
-            dumpTimer(pw, uid, category, BLUETOOTH_MISC_DATA, u.getBluetoothScanTimer(),
-                    rawRealtime, which);
+            final Timer bleTimer = u.getBluetoothScanTimer();
+            if (bleTimer != null) {
+                // Convert from microseconds to milliseconds with rounding
+                final long totalTime = (bleTimer.getTotalTimeLocked(rawRealtime, which) + 500)
+                        / 1000;
+                if (totalTime != 0) {
+                    final int count = bleTimer.getCountLocked(which);
+                    final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
+                    final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
+                    final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+                    // 'actualTime' are unpooled and always since reset (regardless of 'which')
+                    final long actualTime = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
+                    final long actualTimeBg = bleTimerBg != null ?
+                            bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                    dumpLine(pw, uid, category, BLUETOOTH_MISC_DATA, totalTime, count,
+                            countBg, actualTime, actualTimeBg);
+                }
+            }
 
             dumpControllerActivityLine(pw, uid, category, BLUETOOTH_CONTROLLER_DATA,
                     u.getBluetoothControllerActivity(), which);
@@ -3375,16 +3414,21 @@
                 final Uid.Sensor se = sensors.valueAt(ise);
                 final int sensorNumber = sensors.keyAt(ise);
                 final Timer timer = se.getSensorTime();
-                final Counter bgCounter = se.getSensorBgCount();
                 if (timer != null) {
                     // Convert from microseconds to milliseconds with rounding
                     final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
                             / 1000;
-                    final int count = timer.getCountLocked(which);
-                    final int bgCount = bgCounter != null ? bgCounter.getCountLocked(which) : 0;
                     if (totalTime != 0) {
-                        dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime, count,
-                                bgCount);
+                        final int count = timer.getCountLocked(which);
+                        final Timer bgTimer = se.getSensorBackgroundTime();
+                        final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
+                        final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+                        // 'actualTime' are unpooled and always since reset (regardless of 'which')
+                        final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
+                        final long bgActualTime = bgTimer != null ?
+                                bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                        dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime,
+                                count, bgCount, actualTime, bgActualTime);
                     }
                 }
             }
@@ -4294,6 +4338,10 @@
             final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
             final long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
             final int wifiScanCount = u.getWifiScanCount(which);
+            final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
+            // 'actualTime' are unpooled and always since reset (regardless of 'which')
+            final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime);
+            final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime);
             final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
 
             final long mobileWakeup = u.getMobileRadioApWakeupCount(which);
@@ -4344,6 +4392,7 @@
             }
 
             if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
+                    || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0
                     || uidWifiRunningTime != 0) {
                 sb.setLength(0);
                 sb.append(prefix); sb.append("    Wifi Running: ");
@@ -4354,11 +4403,26 @@
                         formatTimeMs(sb, fullWifiLockOnTime / 1000);
                         sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime,
                                 whichBatteryRealtime)); sb.append(")\n");
-                sb.append(prefix); sb.append("    Wifi Scan: ");
+                sb.append(prefix); sb.append("    Wifi Scan (blamed): ");
                         formatTimeMs(sb, wifiScanTime / 1000);
                         sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
                                 whichBatteryRealtime)); sb.append(") ");
                                 sb.append(wifiScanCount);
+                                sb.append("x\n");
+                // actual and background times are unpooled and since reset (regardless of 'which')
+                sb.append(prefix); sb.append("    Wifi Scan (actual): ");
+                        formatTimeMs(sb, wifiScanActualTime / 1000);
+                        sb.append("("); sb.append(formatRatioLocked(wifiScanActualTime,
+                                computeBatteryRealtime(rawRealtime, STATS_SINCE_CHARGED)));
+                                sb.append(") ");
+                                sb.append(wifiScanCount);
+                                sb.append("x\n");
+                sb.append(prefix); sb.append("    Background Wifi Scan: ");
+                        formatTimeMs(sb, wifiScanActualTimeBg / 1000);
+                        sb.append("("); sb.append(formatRatioLocked(wifiScanActualTimeBg,
+                                computeBatteryRealtime(rawRealtime, STATS_SINCE_CHARGED)));
+                                sb.append(") ");
+                                sb.append(wifiScanCountBg);
                                 sb.append("x");
                 pw.println(sb.toString());
             }
@@ -4381,8 +4445,50 @@
                 pw.println(" sent");
             }
 
-            uidActivity |= printTimer(pw, sb, u.getBluetoothScanTimer(), rawRealtime, which, prefix,
-                    "Bluetooth Scan");
+            final Timer bleTimer = u.getBluetoothScanTimer();
+            if (bleTimer != null) {
+                // Convert from microseconds to milliseconds with rounding
+                final long totalTimeMs = (bleTimer.getTotalTimeLocked(rawRealtime, which) + 500)
+                        / 1000;
+                if (totalTimeMs != 0) {
+                    final int count = bleTimer.getCountLocked(which);
+                    final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
+                    final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
+                    final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+                    // 'actualTime' are unpooled and always since reset (regardless of 'which')
+                    final long actualTimeMs = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
+                    final long actualTimeMsBg = bleTimerBg != null ?
+                            bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    ");
+                    sb.append("Bluetooth Scan");
+                    sb.append(": ");
+                    if (actualTimeMs != totalTimeMs) {
+                        formatTimeMs(sb, totalTimeMs);
+                        sb.append("blamed realtime, ");
+                    }
+                    formatTimeMs(sb, actualTimeMs); // since reset, regardless of 'which'
+                    sb.append("realtime (");
+                    sb.append(count);
+                    sb.append(" times)");
+                    if (bleTimer.isRunningLocked()) {
+                            sb.append(" (running)");
+                    }
+                    if (actualTimeMsBg != 0 || countBg > 0) {
+                        sb.append(", ");
+                        formatTimeMs(sb, actualTimeMsBg); // since reset, regardless of 'which'
+                        sb.append("background (");
+                        sb.append(countBg);
+                        sb.append(" times)");
+                    }
+                    pw.println(sb.toString());
+                    uidActivity = true;
+                }
+            }
+
+
 
             if (u.hasUserActivity()) {
                 boolean hasData = false;
@@ -4553,25 +4659,38 @@
                 sb.append(": ");
 
                 final Timer timer = se.getSensorTime();
-                final Counter bgCounter = se.getSensorBgCount();
                 if (timer != null) {
                     // Convert from microseconds to milliseconds with rounding
-                    final long totalTime = (timer.getTotalTimeLocked(
-                            rawRealtime, which) + 500) / 1000;
+                    final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
+                            / 1000;
                     final int count = timer.getCountLocked(which);
-                    final int bgCount = bgCounter != null ? bgCounter.getCountLocked(which) : 0;
+                    final Timer bgTimer = se.getSensorBackgroundTime();
+                    final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
+                    final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+                    // 'actualTime' are unpooled and always since reset (regardless of 'which')
+                    final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
+                    final long bgActualTime = bgTimer != null ?
+                            bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+
                     //timer.logState();
                     if (totalTime != 0) {
-                        formatTimeMs(sb, totalTime);
+                        if (actualTime != totalTime) {
+                            formatTimeMs(sb, totalTime);
+                            sb.append("blamed realtime, ");
+                        }
+
+                        formatTimeMs(sb, actualTime); // since reset, regardless of 'which'
                         sb.append("realtime (");
                         sb.append(count);
-                        sb.append(" times");
-                        if (bgCount > 0) {
+                        sb.append(" times)");
+
+                        if (bgActualTime != 0 || bgCount > 0) {
                             sb.append(", ");
+                            formatTimeMs(sb, bgActualTime); // since reset, regardless of 'which'
+                            sb.append("background (");
                             sb.append(bgCount);
-                            sb.append(" bg");
+                            sb.append(" times)");
                         }
-                        sb.append(")");
                     } else {
                         sb.append("(not used)");
                     }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2e35a51..76128e6 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2039,14 +2039,20 @@
         }
     }
 
+    /** @deprecated use {@link android.system.Os#open(String, int, int)} */
+    @Deprecated
+    static native FileDescriptor openFileDescriptor(String file, int mode)
+            throws FileNotFoundException;
 
-    /*package*/ static native FileDescriptor openFileDescriptor(String file,
-            int mode) throws FileNotFoundException;
-    /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
-            throws IOException;
-    /*package*/ static native void closeFileDescriptor(FileDescriptor desc)
-            throws IOException;
-    /*package*/ static native void clearFileDescriptor(FileDescriptor desc);
+    /** @deprecated use {@link android.system.Os#dup(FileDescriptor)} */
+    @Deprecated
+    static native FileDescriptor dupFileDescriptor(FileDescriptor orig) throws IOException;
+
+    /** @deprecated use {@link android.system.Os#close(FileDescriptor)} */
+    @Deprecated
+    static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
+
+    static native void clearFileDescriptor(FileDescriptor desc);
 
     /**
      * Read a byte value from the parcel at the current dataPosition().
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 8882672..3212139 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -17,11 +17,21 @@
 package android.os;
 
 import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDONLY;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.O_TRUNC;
+import static android.system.OsConstants.O_WRONLY;
 import static android.system.OsConstants.SEEK_SET;
-import static android.system.OsConstants.SOCK_STREAM;
 import static android.system.OsConstants.SOCK_SEQPACKET;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.S_IROTH;
+import static android.system.OsConstants.S_IRWXG;
+import static android.system.OsConstants.S_IRWXU;
 import static android.system.OsConstants.S_ISLNK;
 import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_IWOTH;
 
 import android.content.BroadcastReceiver;
 import android.content.ContentProvider;
@@ -33,6 +43,7 @@
 import android.util.Log;
 
 import dalvik.system.CloseGuard;
+
 import libcore.io.IoUtils;
 import libcore.io.Memory;
 
@@ -279,8 +290,28 @@
                     "Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
         }
 
+        int flags = 0;
+        switch (mode & MODE_READ_WRITE) {
+            case 0:
+            case MODE_READ_ONLY: flags = O_RDONLY; break;
+            case MODE_WRITE_ONLY: flags = O_WRONLY; break;
+            case MODE_READ_WRITE: flags = O_RDWR; break;
+        }
+
+        if ((mode & MODE_CREATE) != 0) flags |= O_CREAT;
+        if ((mode & MODE_TRUNCATE) != 0) flags |= O_TRUNC;
+        if ((mode & MODE_APPEND) != 0) flags |= O_APPEND;
+
+        int realMode = S_IRWXU | S_IRWXG;
+        if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH;
+        if ((mode & MODE_WORLD_WRITEABLE) != 0) realMode |= S_IWOTH;
+
         final String path = file.getPath();
-        return Parcel.openFileDescriptor(path, mode);
+        try {
+            return Os.open(path, flags, realMode);
+        } catch (ErrnoException e) {
+            throw new FileNotFoundException(e.getMessage());
+        }
     }
 
     /**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 53c9a23..7e1b5ab 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -113,6 +113,8 @@
     public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
     /** {@hide} */
     public static final String PROP_SDCARDFS = "persist.sys.sdcardfs";
+    /** {@hide} */
+    public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk";
 
     /** {@hide} */
     public static final String UUID_PRIVATE_INTERNAL = null;
@@ -140,6 +142,8 @@
     public static final int DEBUG_SDCARDFS_FORCE_ON = 1 << 2;
     /** {@hide} */
     public static final int DEBUG_SDCARDFS_FORCE_OFF = 1 << 3;
+    /** {@hide} */
+    public static final int DEBUG_VIRTUAL_DISK = 1 << 4;
 
     // NOTE: keep in sync with installd
     /** {@hide} */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8352874..7005d44 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5482,6 +5482,20 @@
         public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled";
 
         /**
+         * Setting specifying if the accessibility shortcut is enabled.
+         * @hide
+         */
+        public static final String ACCESSIBILITY_SHORTCUT_ENABLED =
+                "accessibility_shortcut_enabled";
+
+        /**
+         * Setting specifying if the accessibility shortcut is enabled.
+         * @hide
+         */
+        public static final String ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN =
+                "accessibility_shortcut_on_lock_screen";
+
+        /**
          * Setting specifying if the accessibility shortcut dialog has been shown to this user.
          * @hide
          */
@@ -5489,7 +5503,7 @@
                 "accessibility_shortcut_dialog_shown";
 
         /**
-         * Setting specifying the the accessibility service to be toggled via the accessibility
+         * Setting specifying the accessibility service to be toggled via the accessibility
          * shortcut. Must be its flattened {@link ComponentName}.
          * @hide
          */
@@ -5497,6 +5511,16 @@
                 "accessibility_shortcut_target_service";
 
         /**
+         * Setting specifying the accessibility service or feature to be toggled via the
+         * accessibility button in the navigation bar. This is either a flattened
+         * {@link ComponentName} or the class name of a system class implementing a supported
+         * accessibility feature.
+         * @hide
+         */
+        public static final String ACCESSIBILITY_BUTTON_TARGET_COMPONENT =
+                "accessibility_button_target_component";
+
+        /**
          * If touch exploration is enabled.
          */
         public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
@@ -6983,7 +7007,10 @@
             TOUCH_EXPLORATION_ENABLED,
             ACCESSIBILITY_ENABLED,
             ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+            ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
             ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+            ACCESSIBILITY_SHORTCUT_ENABLED,
+            ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
             ACCESSIBILITY_SPEAK_PASSWORD,
             ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
             ACCESSIBILITY_CAPTIONING_PRESET,
@@ -9731,6 +9758,15 @@
         public static final String RETAIL_DEMO_MODE_CONSTANTS = "retail_demo_mode_constants";
 
         /**
+         * Indicates the maximum time that an app is blocked for the network rules to get updated.
+         *
+         * Type: long
+         *
+         * @hide
+         */
+        public static final String NETWORK_ACCESS_TIMEOUT_MS = "network_access_timeout_ms";
+
+        /**
          * The reason for the settings database being downgraded. This is only for
          * troubleshooting purposes and its value should not be interpreted in any way.
          *
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index c9f9f31..35276cc 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -22,6 +22,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SdkConstant;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -97,6 +98,22 @@
     /** @hide */
     public static final int AUTOCLICK_DELAY_DEFAULT = 600;
 
+    /**
+     * Activity action: Launch UI to manage which accessibility service or feature is assigned
+     * to the navigation bar Accessibility button.
+     * <p>
+     * Input: Nothing.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
+            "android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
+
     static final Object sInstanceSync = new Object();
 
     private static AccessibilityManager sInstance;
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 9ed6371..b4d2c6b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -26,14 +26,20 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.graphics.Rect;
+import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.View;
 import android.view.WindowManagerGlobal;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -83,7 +89,7 @@
     /** @hide */ public static final int FLAG_VIEW_EXITED =   0x20000000;
     /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x10000000;
 
-    @NonNull private final Rect mTempRect = new Rect();
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     private final IAutoFillManager mService;
     private IAutoFillManagerClient mServiceClient;
@@ -98,25 +104,37 @@
     /** @hide */
     public interface AutofillClient {
         /**
-         * Asks the client to perform an autofill.
-         *
-         * @param ids The values to autofill
-         * @param values The values to autofill
-         */
-        void autofill(List<AutofillId> ids, List<AutofillValue> values);
-
-        /**
          * Asks the client to start an authentication flow.
          *
          * @param intent The authentication intent.
          * @param fillInIntent The authentication fill-in intent.
          */
-        void authenticate(IntentSender intent, Intent fillInIntent);
+        void autofillCallbackAuthenticate(IntentSender intent, Intent fillInIntent);
 
         /**
          * Tells the client this manager has state to be reset.
          */
-        void resetableStateAvailable();
+        void autofillCallbackResetableStateAvailable();
+
+        /**
+         * Request showing the autofill UI.
+         *
+         * @param anchor The real view the UI needs to anchor to.
+         * @param width The width of the fill UI content.
+         * @param height The height of the fill UI content.
+         * @param virtualBounds The bounds of the virtual decendant of the anchor.
+         * @param presenter The presenter that controls the fill UI window.
+         * @return Whether the UI was shown.
+         */
+        boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width, int height,
+                @Nullable Rect virtualBounds, IAutofillWindowPresenter presenter);
+
+        /**
+         * Request hiding the autofill UI.
+         *
+         * @return Whether the UI was hidden.
+         */
+        boolean autofillCallbackRequestHideFillUi();
     }
 
     /**
@@ -156,12 +174,10 @@
             return;
         }
 
-        final Rect bounds = mTempRect;
-        view.getBoundsOnScreen(bounds);
         final AutofillId id = getAutofillId(view);
         final AutofillValue value = view.getAutofillValue();
 
-        startSession(id, view.getWindowToken(), bounds, value, FLAG_MANUAL_REQUEST);
+        startSession(id, view.getWindowToken(), null, value, FLAG_MANUAL_REQUEST);
     }
 
     /**
@@ -202,17 +218,15 @@
             return;
         }
 
-        final Rect bounds = mTempRect;
-        view.getBoundsOnScreen(bounds);
         final AutofillId id = getAutofillId(view);
         final AutofillValue value = view.getAutofillValue();
 
         if (!mHasSession) {
             // Starts new session.
-            startSession(id, view.getWindowToken(), bounds, value, 0);
+            startSession(id, view.getWindowToken(), null, value, 0);
         } else {
             // Update focus on existing session.
-            updateSession(id, bounds, value, FLAG_VIEW_ENTERED);
+            updateSession(id, null, value, FLAG_VIEW_ENTERED);
         }
     }
 
@@ -389,7 +403,7 @@
                     mCallback != null, flags, mContext.getOpPackageName());
             AutofillClient client = getClient();
             if (client != null) {
-                client.resetableStateAvailable();
+                client.autofillCallbackResetableStateAvailable();
             }
             mHasSession = true;
         } catch (RemoteException e) {
@@ -490,28 +504,114 @@
         }
     }
 
-    private void onAutofillEvent(IBinder windowToken, AutofillId id, int event) {
-        if (mCallback == null) return;
-        if (id == null) {
-            Log.w(TAG, "onAutofillEvent(): no id for event " + event);
+    private void requestShowFillUi(IBinder windowToken, AutofillId id, int width, int height,
+            Rect anchorBounds, IAutofillWindowPresenter presenter) {
+        final View anchor = findAchorView(windowToken, id);
+        if (anchor == null) {
+            return;
+        }
+        if (getClient().autofillCallbackRequestShowFillUi(anchor, width, height,
+                anchorBounds, presenter) && mCallback != null) {
+            if (id.isVirtual()) {
+                mCallback.onAutofillEvent(anchor, id.getVirtualChildId(),
+                        AutofillCallback.EVENT_INPUT_SHOWN);
+            } else {
+                mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_SHOWN);
+            }
+        }
+    }
+
+    private void handleAutofill(IBinder windowToken, List<AutofillId> ids,
+            List<AutofillValue> values) {
+        final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
+        if (root == null) {
             return;
         }
 
+        final int itemCount = ids.size();
+        int numApplied = 0;
+        ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
+
+        for (int i = 0; i < itemCount; i++) {
+            final AutofillId id = ids.get(i);
+            final AutofillValue value = values.get(i);
+            final int viewId = id.getViewId();
+            final View view = root.findViewByAccessibilityIdTraversal(viewId);
+            if (view == null) {
+                Log.w(TAG, "autofill(): no View with id " + viewId);
+                continue;
+            }
+            if (id.isVirtual()) {
+                if (virtualValues == null) {
+                    // Most likely there will be just one view with virtual children.
+                    virtualValues = new ArrayMap<>(1);
+                }
+                SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
+                if (valuesByParent == null) {
+                    // We don't know the size yet, but usually it will be just a few fields...
+                    valuesByParent = new SparseArray<>(5);
+                    virtualValues.put(view, valuesByParent);
+                }
+                valuesByParent.put(id.getVirtualChildId(), value);
+            } else {
+                if (view.autofill(value)) {
+                    numApplied++;
+                }
+            }
+        }
+
+        if (virtualValues != null) {
+            for (int i = 0; i < virtualValues.size(); i++) {
+                final View parent = virtualValues.keyAt(i);
+                final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
+                if (parent.autofill(childrenValues)) {
+                    numApplied += childrenValues.size();
+                }
+            }
+        }
+
+        final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
+        log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
+        log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
+        mMetricsLogger.write(log);
+    }
+
+    private void requestHideFillUi(IBinder windowToken, AutofillId id) {
+        if (getClient().autofillCallbackRequestHideFillUi() && mCallback != null) {
+            final View anchor = findAchorView(windowToken, id);
+            if (id.isVirtual()) {
+                mCallback.onAutofillEvent(anchor, id.getVirtualChildId(),
+                        AutofillCallback.EVENT_INPUT_HIDDEN);
+            } else {
+                mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_HIDDEN);
+            }
+        }
+    }
+
+    private void notifyNoFillUi(IBinder windowToken, AutofillId id) {
+        if (mCallback != null) {
+            final View anchor = findAchorView(windowToken, id);
+            if (id.isVirtual()) {
+                mCallback.onAutofillEvent(anchor, id.getVirtualChildId(),
+                        AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+            } else {
+                mCallback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+            }
+        }
+    }
+
+    private View findAchorView(IBinder windowToken, AutofillId id) {
         final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
         if (root == null) {
-            Log.w(TAG, "onAutofillEvent() for " + id + ": root view gone");
-            return;
+            Log.w(TAG, "no window with token " + windowToken);
+            return null;
         }
         final View view = root.findViewByAccessibilityIdTraversal(id.getViewId());
         if (view == null) {
-            Log.w(TAG, "onAutofillEvent() for " + id + ": view gone");
-            return;
+            Log.w(TAG, "no view with id " + id);
+            return null;
         }
-        if (id.isVirtual()) {
-            mCallback.onAutofillEvent(view, id.getVirtualChildId(), event);
-        } else {
-            mCallback.onAutofillEvent(view, event);
-        }
+        return view;
     }
 
     /**
@@ -590,16 +690,14 @@
         }
 
         @Override
-        public void autofill(List<AutofillId> ids, List<AutofillValue> values) {
+        public void autofill(IBinder windowToken, List<AutofillId> ids,
+                List<AutofillValue> values) {
             // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
             // dataset.extras to service
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.mContext.getMainThreadHandler().post(() -> {
-                    if (afm.getClient() != null) {
-                        afm.getClient().autofill(ids, values);
-                    }
-                });
+                afm.mContext.getMainThreadHandler().post(() ->
+                        afm.handleAutofill(windowToken, ids, values));
             }
         }
 
@@ -609,19 +707,45 @@
             if (afm != null) {
                 afm.mContext.getMainThreadHandler().post(() -> {
                     if (afm.getClient() != null) {
-                        afm.getClient().authenticate(intent, fillInIntent);
+                        afm.getClient().autofillCallbackAuthenticate(intent, fillInIntent);
                     }
                 });
             }
         }
 
         @Override
-        public void onAutofillEvent(IBinder windowToken, AutofillId id, int event) {
+        public void requestShowFillUi(IBinder windowToken, AutofillId id,
+                int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
                 afm.mContext.getMainThreadHandler().post(() -> {
                     if (afm.getClient() != null) {
-                        afm.onAutofillEvent(windowToken, id, event);
+                        afm.requestShowFillUi(windowToken, id, width,
+                                height, anchorBounds, presenter);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void requestHideFillUi(IBinder windowToken, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.mContext.getMainThreadHandler().post(() -> {
+                    if (afm.getClient() != null) {
+                        afm.requestHideFillUi(windowToken, id);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void notifyNoFillUi(IBinder windowToken, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.mContext.getMainThreadHandler().post(() -> {
+                    if (afm.getClient() != null) {
+                        afm.notifyNoFillUi(windowToken, id);
                     }
                 });
             }
diff --git a/core/java/android/view/autofill/AutofillPopupWindow.java b/core/java/android/view/autofill/AutofillPopupWindow.java
new file mode 100644
index 0000000..056e540
--- /dev/null
+++ b/core/java/android/view/autofill/AutofillPopupWindow.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.transition.Transition;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.PopupWindow;
+
+/**
+ * Custom {@link PopupWindow} used to isolate its content from the autofilled app - the
+ * UI is rendered in a framework process, but it's controlled by the app.
+ *
+ * TODO(b/34943932): use an app surface control solution.
+ *
+ * @hide
+ */
+public class AutofillPopupWindow extends PopupWindow {
+
+    private static final String TAG = "AutofillPopupWindow";
+
+    private final WindowPresenter mWindowPresenter;
+    private WindowManager.LayoutParams mWindowLayoutParams;
+
+    /**
+     * Creates a popup window with a presenter owning the window and responsible for
+     * showing/hiding/updating the backing window. This can be useful of the window is
+     * being shown by another process while the popup logic is in the process hosting
+     * the anchor view.
+     * <p>
+     * Using this constructor means that the presenter completely owns the content of
+     * the window and the following methods manipulating the window content shouldn't
+     * be used: {@link #getEnterTransition()}, {@link #setEnterTransition(Transition)},
+     * {@link #getExitTransition()}, {@link #setExitTransition(Transition)},
+     * {@link #getContentView()}, {@link #setContentView(View)}, {@link #getBackground()},
+     * {@link #setBackgroundDrawable(Drawable)}, {@link #getElevation()},
+     * {@link #setElevation(float)}, ({@link #getAnimationStyle()},
+     * {@link #setAnimationStyle(int)}, {@link #setTouchInterceptor(OnTouchListener)}.</p>
+     */
+    public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) {
+        mWindowPresenter = new WindowPresenter(presenter);
+
+        setOutsideTouchable(true);
+        setInputMethodMode(INPUT_METHOD_NEEDED);
+    }
+
+    @Override
+    protected boolean hasContentView() {
+        return true;
+    }
+
+    @Override
+    protected boolean hasDecorView() {
+        return true;
+    }
+
+    @Override
+    protected LayoutParams getDecorViewLayoutParams() {
+        return mWindowLayoutParams;
+    }
+
+    /**
+     * The effective {@code update} method that should be called by its clients.
+     */
+    public void update(View anchor, int offsetX, int offsetY, int width, int height,
+            Rect anchorBounds, Rect actualAnchorBounds) {
+        if (!isShowing()) {
+            setWidth(width);
+            setHeight(height);
+            showAsDropDown(anchor, offsetX, offsetY);
+        } else {
+            update(anchor, offsetX, offsetY, width, height);
+        }
+
+        if (anchorBounds != null && mWindowLayoutParams.y > anchorBounds.bottom) {
+            offsetY = anchorBounds.bottom - actualAnchorBounds.bottom;
+            update(anchor, offsetX, offsetY, width, height);
+        }
+    }
+
+    @Override
+    protected void update(View anchor, WindowManager.LayoutParams params) {
+        final int layoutDirection = anchor != null ? anchor.getLayoutDirection()
+                : View.LAYOUT_DIRECTION_LOCALE;
+        mWindowPresenter.show(params, getTransitionEpicenter(), isLayoutInsetDecor(),
+                layoutDirection);
+    }
+
+    @Override
+    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
+        if (isShowing()) {
+            return;
+        }
+
+        setShowing(true);
+        setDropDown(true);
+        attachToAnchor(anchor, xoff, yoff, gravity);
+        final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams(
+                anchor.getWindowToken());
+        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
+                p.width, p.height, gravity, getAllowScrollingAnchorParent());
+        updateAboveAnchor(aboveAnchor);
+        p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+        p.packageName = anchor.getContext().getPackageName();
+        mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
+                anchor.getLayoutDirection());
+        return;
+    }
+
+    @Override
+    public void dismiss() {
+        if (!isShowing() || isTransitioningToDismiss()) {
+            return;
+        }
+
+        setShowing(false);
+        setTransitioningToDismiss(true);
+
+        mWindowPresenter.hide(getTransitionEpicenter());
+        detachFromAnchor();
+        if (getOnDismissListener() != null) {
+            getOnDismissListener().onDismiss();
+        }
+    }
+
+    @Override
+    public int getAnimationStyle() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public Drawable getBackground() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public View getContentView() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public float getElevation() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public Transition getEnterTransition() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public Transition getExitTransition() {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setAnimationStyle(int animationStyle) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setBackgroundDrawable(Drawable background) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setContentView(View contentView) {
+        if (contentView != null) {
+            throw new IllegalStateException("You can't call this!");
+        }
+    }
+
+    @Override
+    public void setElevation(float elevation) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setEnterTransition(Transition enterTransition) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setExitTransition(Transition exitTransition) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    @Override
+    public void setTouchInterceptor(OnTouchListener l) {
+        throw new IllegalStateException("You can't call this!");
+    }
+
+    /**
+     * Contract between the popup window and a presenter that is responsible for
+     * showing/hiding/updating the actual window.
+     *
+     * <p>This can be useful if the anchor is in one process and the backing window is owned by
+     * another process.
+     */
+    private class WindowPresenter {
+        final IAutofillWindowPresenter mPresenter;
+
+        WindowPresenter(IAutofillWindowPresenter presenter) {
+            mPresenter = presenter;
+        }
+
+        /**
+         * Shows the backing window.
+         *
+         * @param p The window layout params.
+         * @param transitionEpicenter The transition epicenter if animating.
+         * @param fitsSystemWindows Whether the content view should account for system decorations.
+         * @param layoutDirection The content layout direction to be consistent with the anchor.
+         */
+        void show(WindowManager.LayoutParams p, Rect transitionEpicenter, boolean fitsSystemWindows,
+                int layoutDirection) {
+            try {
+                mPresenter.show(p, transitionEpicenter, fitsSystemWindows, layoutDirection);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error showing fill window", e);
+                e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Hides the backing window.
+         *
+         * @param transitionEpicenter The transition epicenter if animating.
+         */
+        void hide(Rect transitionEpicenter) {
+            try {
+                mPresenter.hide(transitionEpicenter);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error hiding fill window", e);
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index eabf6b1..7bea174 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -20,9 +20,11 @@
 
 import android.content.Intent;
 import android.content.IntentSender;
+import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
 
 /**
  * Object running in the application process and responsible for autofilling it.
@@ -38,7 +40,7 @@
     /**
       * Autofills the activity with the contents of a dataset.
       */
-    void autofill(in List<AutofillId> ids, in List<AutofillValue> values);
+    void autofill(in IBinder windowToken, in List<AutofillId> ids, in List<AutofillValue> values);
 
     /**
       * Authenticates a fill response or a data set.
@@ -46,7 +48,18 @@
     void authenticate(in IntentSender intent, in Intent fillInIntent);
 
     /**
-     * Notifies the client when the auto-fill UI changed.
+     * Requests showing the fill UI.
      */
-    void onAutofillEvent(in IBinder windowToken, in AutofillId id, int event);
+    void requestShowFillUi(in IBinder windowToken, in AutofillId id, int width,
+            int height, in Rect anchorBounds, in IAutofillWindowPresenter presenter);
+
+    /**
+     * Requests hiding the fill UI.
+     */
+    void requestHideFillUi(in IBinder windowToken, in AutofillId id);
+
+    /**
+     * Nitifies no fill UI will be shown.
+     */
+    void notifyNoFillUi(in IBinder windowToken, in AutofillId id);
 }
diff --git a/core/java/android/view/autofill/IAutofillWindowPresenter.aidl b/core/java/android/view/autofill/IAutofillWindowPresenter.aidl
new file mode 100644
index 0000000..172b992
--- /dev/null
+++ b/core/java/android/view/autofill/IAutofillWindowPresenter.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+/**
+ * This is a handle to the FillUi for controlling
+ * when its window should be shown and hidden.
+ *
+ * {@hide}
+ */
+oneway interface IAutofillWindowPresenter {
+    void show(in WindowManager.LayoutParams p, in Rect transitionEpicenter,
+            boolean fitsSystemWindows, int layoutDirection);
+    void hide(in Rect transitionEpicenter);
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 46f7a81..dabbf31 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -70,26 +70,6 @@
         public LinksInfo getLinks(CharSequence text, int linkMask, LocaleList defaultLocales) {
             return LinksInfo.NO_OP;
         }
-
-        // TODO: Remove
-        @Override
-        public TextSelection suggestSelection(
-                CharSequence text, int selectionStartIndex, int selectionEndIndex) {
-            throw new UnsupportedOperationException("Removed");
-        }
-
-        // TODO: Remove
-        @Override
-        public TextClassificationResult getTextClassificationResult(
-                CharSequence text, int startIndex, int endIndex) {
-            throw new UnsupportedOperationException("Removed");
-        }
-
-        // TODO: Remove
-        @Override
-        public LinksInfo getLinks(CharSequence text, int linkMask) {
-            throw new UnsupportedOperationException("Removed");
-        }
     };
 
     /**
@@ -154,16 +134,4 @@
      */
     LinksInfo getLinks(
             @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales);
-
-    // TODO: Remove
-    /** @removed */
-    TextSelection suggestSelection(
-            CharSequence text, int selectionStartIndex, int selectionEndIndex);
-    // TODO: Remove
-    /** @removed */
-    TextClassificationResult getTextClassificationResult(
-            CharSequence text, int startIndex, int endIndex);
-    // TODO: Remove
-    /** @removed */
-    LinksInfo getLinks(CharSequence text, int linkMask);
 }
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 06ac869..be12f57 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -120,10 +120,12 @@
                 SmartSelection.ClassificationResult[] results = getSmartSelection()
                         .classifyText(text.toString(), startIndex, endIndex);
                 if (results.length > 0) {
+                    final TextClassificationResult classificationResult =
+                            createClassificationResult(results, classified);
                     // TODO: Added this log for debug only. Remove before release.
                     Log.d(LOG_TAG, String.format(
-                            "Classification type: %s", getHighestScoringType(results)));
-                    return createClassificationResult(results, classified);
+                            "Classification type: %s", classificationResult));
+                    return classificationResult;
                 }
             }
         } catch (Throwable t) {
@@ -149,26 +151,6 @@
         return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
     }
 
-    // TODO: Remove
-    @Override
-    public TextSelection suggestSelection(
-            CharSequence text, int selectionStartIndex, int selectionEndIndex) {
-        throw new UnsupportedOperationException("Removed");
-    }
-
-    // TODO: Remove
-    @Override
-    public TextClassificationResult getTextClassificationResult(
-            CharSequence text, int startIndex, int endIndex) {
-        throw new UnsupportedOperationException("Removed");
-    }
-
-    // TODO: Remove
-    @Override
-    public LinksInfo getLinks(CharSequence text, int linkMask) {
-        throw new UnsupportedOperationException("Removed");
-    }
-
     private SmartSelection getSmartSelection() throws FileNotFoundException {
         synchronized (mSmartSelectionLock) {
             if (mSmartSelection == null) {
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index fae5742..8f662ba 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -498,18 +498,30 @@
     public void onProvideAutofillStructure(ViewStructure structure, int flags) {
         super.onProvideAutofillStructure(structure, flags);
 
-        if (getAdapter() == null) return;
+        final SpinnerAdapter adapter = getAdapter();
+
+        if (adapter == null) return;
 
         // TODO(b/33197203): implement sanitization so initial value is only sanitized when coming
         // from resources.
 
-        final int count = getAdapter().getCount();
+        final int count = adapter.getCount();
+        int size = 0;
         if (count > 0) {
             final String[] options = new String[count];
             for (int i = 0; i < count; i++) {
-                options[i] = getAdapter().getItem(i).toString();
+                final Object item = adapter.getItem(i);
+                if (item != null) {
+                    options[size++] = item.toString();
+                }
             }
-            structure.setAutofillOptions(options);
+            if (size == count) {
+                structure.setAutofillOptions(options);
+            } else {
+                final String[] validOptions = new String[size];
+                System.arraycopy(options, 0, validOptions, 0, size);
+                structure.setAutofillOptions(validOptions);
+            }
         }
     }
 
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 9f10531..7d243af 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -209,7 +209,7 @@
     private int mGravity = Gravity.NO_GRAVITY;
 
     private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
-        com.android.internal.R.attr.state_above_anchor
+            com.android.internal.R.attr.state_above_anchor
     };
 
     private final OnAttachStateChangeListener mOnAnchorDetachedListener =
@@ -589,7 +589,6 @@
         mIgnoreCheekPress = true;
     }
 
-
     /**
      * <p>Change the animation style resource for this popup.</p>
      *
@@ -860,6 +859,11 @@
         mAllowScrollingAnchorParent = enabled;
     }
 
+    /** @hide */
+    protected final boolean getAllowScrollingAnchorParent() {
+        return mAllowScrollingAnchorParent;
+    }
+
     /**
      * <p>Indicates whether the popup window supports splitting touches.</p>
      *
@@ -960,6 +964,11 @@
         mLayoutInsetDecor = enabled;
     }
 
+    /** @hide */
+    protected final boolean isLayoutInsetDecor() {
+        return mLayoutInsetDecor;
+    }
+
     /**
      * Set the layout type for this window.
      * <p>
@@ -1122,6 +1131,26 @@
         return mIsShowing;
     }
 
+    /** @hide */
+    protected final void setShowing(boolean isShowing) {
+        mIsShowing = isShowing;
+    }
+
+    /** @hide */
+    protected final void setDropDown(boolean isDropDown) {
+        mIsDropdown = isDropDown;
+    }
+
+    /** @hide */
+    protected final void setTransitioningToDismiss(boolean transitioningToDismiss) {
+        mIsTransitioningToDismiss = transitioningToDismiss;
+    }
+
+    /** @hide */
+    protected final boolean isTransitioningToDismiss() {
+        return mIsTransitioningToDismiss;
+    }
+
     /**
      * <p>
      * Display the content view in a popup window at the specified location. If the popup window
@@ -1232,7 +1261,7 @@
      * @see #dismiss()
      */
     public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
-        if (isShowing() || mContentView == null) {
+        if (isShowing() || !hasContentView()) {
             return;
         }
 
@@ -1255,7 +1284,8 @@
         invokePopup(p);
     }
 
-    private void updateAboveAnchor(boolean aboveAnchor) {
+    /** @hide */
+    protected final void updateAboveAnchor(boolean aboveAnchor) {
         if (aboveAnchor != mAboveAnchor) {
             mAboveAnchor = aboveAnchor;
 
@@ -1426,8 +1456,10 @@
      * @param token the window token used to bind the popup's window
      *
      * @return the layout parameters to pass to the window manager
+     *
+     * @hide
      */
-    private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
+    protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
 
         // These gravity settings put the view at the top left corner of the
@@ -1510,7 +1542,7 @@
             curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
         }
         if (mAttachedInDecor) {
-          curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
+            curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
         }
         return curFlags;
     }
@@ -1543,8 +1575,10 @@
      * @param allowScroll whether the anchor view's parent may be scrolled
      *                    when the popup window doesn't fit on screen
      * @return true if the popup is translated upwards to fit on screen
+     *
+     * @hide
      */
-    private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
+    protected final boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
             int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
         final int anchorHeight = anchor.getHeight();
         final int anchorWidth = anchor.getWidth();
@@ -1853,7 +1887,7 @@
      * @see #showAsDropDown(android.view.View)
      */
     public void dismiss() {
-        if (!isShowing() || mIsTransitioningToDismiss) {
+        if (!isShowing() || isTransitioningToDismiss()) {
             return;
         }
 
@@ -1923,8 +1957,10 @@
      *
      * @return the window-relative epicenter bounds to be used by enter and
      *         exit transitions
+     *
+     * @hide
      */
-    private Rect getTransitionEpicenter() {
+    protected final Rect getTransitionEpicenter() {
         final View anchor = mAnchor != null ? mAnchor.get() : null;
         final View decor = mDecorView;
         if (anchor == null || decor == null) {
@@ -1981,6 +2017,11 @@
         mOnDismissListener = onDismissListener;
     }
 
+    /** @hide */
+    protected final OnDismissListener getOnDismissListener() {
+        return mOnDismissListener;
+    }
+
     /**
      * Updates the state of the popup window, if it is currently being displayed,
      * from the currently set state.
@@ -1996,12 +2037,11 @@
      * </ul>
      */
     public void update() {
-        if (!isShowing() || mContentView == null) {
+        if (!isShowing() || !hasContentView()) {
             return;
         }
 
-        final WindowManager.LayoutParams p =
-                (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+        final WindowManager.LayoutParams p = getDecorViewLayoutParams();
 
         boolean update = false;
 
@@ -2024,11 +2064,16 @@
         }
 
         if (update) {
-            setLayoutDirectionFromAnchor();
-            mWindowManager.updateViewLayout(mDecorView, p);
+            update(mAnchor.get(), p);
         }
     }
 
+    /** @hide */
+    protected void update(View anchor, WindowManager.LayoutParams params) {
+        setLayoutDirectionFromAnchor();
+        mWindowManager.updateViewLayout(mDecorView, params);
+    }
+
     /**
      * Updates the dimension of the popup window.
      * <p>
@@ -2039,8 +2084,7 @@
      * @param height the new height in pixels, must be >= 0 or -1 to ignore
      */
     public void update(int width, int height) {
-        final WindowManager.LayoutParams p =
-                (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+        final WindowManager.LayoutParams p = getDecorViewLayoutParams();
         update(p.x, p.y, width, height, false);
     }
 
@@ -2086,12 +2130,11 @@
             setHeight(height);
         }
 
-        if (!isShowing() || mContentView == null) {
+        if (!isShowing() || !hasContentView()) {
             return;
         }
 
-        final WindowManager.LayoutParams p =
-                (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+        final WindowManager.LayoutParams p = getDecorViewLayoutParams();
 
         boolean update = force;
 
@@ -2135,19 +2178,39 @@
             update = true;
         }
 
-        int newAccessibilityIdOfAnchor =
-                (mAnchor != null) ? mAnchor.get().getAccessibilityViewId() : -1;
+        View anchor = null;
+        int newAccessibilityIdOfAnchor = -1;
+
+        if (mAnchor != null && mAnchor.get() != null) {
+            anchor = mAnchor.get();
+            newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+        }
+
         if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
             p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
             update = true;
         }
 
         if (update) {
-            setLayoutDirectionFromAnchor();
-            mWindowManager.updateViewLayout(mDecorView, p);
+            update(anchor, p);
         }
     }
 
+    /** @hide */
+    protected boolean hasContentView() {
+        return mContentView != null;
+    }
+
+    /** @hide */
+    protected boolean hasDecorView() {
+        return mDecorView != null;
+    }
+
+    /** @hide */
+    protected WindowManager.LayoutParams getDecorViewLayoutParams() {
+        return (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+    }
+
     /**
      * Updates the position and the dimension of the popup window.
      * <p>
@@ -2185,7 +2248,7 @@
     private void update(View anchor, boolean updateLocation, int xoff, int yoff,
             int width, int height) {
 
-        if (!isShowing() || mContentView == null) {
+        if (!isShowing() || !hasContentView()) {
             return;
         }
 
@@ -2201,7 +2264,7 @@
             mAnchorYoff = yoff;
         }
 
-        final LayoutParams p = (LayoutParams) mDecorView.getLayoutParams();
+        final WindowManager.LayoutParams p = getDecorViewLayoutParams();
         final int oldGravity = p.gravity;
         final int oldWidth = p.width;
         final int oldHeight = p.height;
@@ -2243,7 +2306,8 @@
         public void onDismiss();
     }
 
-    private void detachFromAnchor() {
+    /** @hide */
+    protected final void detachFromAnchor() {
         final View anchor = mAnchor != null ? mAnchor.get() : null;
         if (anchor != null) {
             final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2262,7 +2326,8 @@
         mIsAnchorRootAttached = false;
     }
 
-    private void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+    /** @hide */
+    protected final void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
         detachFromAnchor();
 
         final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2287,9 +2352,8 @@
 
     private void alignToAnchor() {
         final View anchor = mAnchor != null ? mAnchor.get() : null;
-        if (anchor != null && anchor.isAttachedToWindow() && mDecorView != null) {
-            final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
-                    mDecorView.getLayoutParams();
+        if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) {
+            final WindowManager.LayoutParams p = getDecorViewLayoutParams();
 
             updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
                     p.width, p.height, mAnchoredGravity, false));
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
new file mode 100644
index 0000000..ee5d339
--- /dev/null
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.app;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Activity used to display and persist a service or feature target for the Accessibility button.
+ */
+public class AccessibilityButtonChooserActivity extends Activity {
+
+    private static final String MAGNIFICATION_COMPONENT_ID =
+            "com.android.server.accessibility.MagnificationController";
+
+    private AccessibilityButtonTarget mMagnificationTarget = null;
+
+    private List<AccessibilityButtonTarget> mTargets = null;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.accessibility_button_chooser);
+
+        String component = Settings.Secure.getString(getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+        if (TextUtils.isEmpty(component)) {
+            TextView prompt = (TextView) findViewById(R.id.accessibility_button_prompt);
+            prompt.setVisibility(View.VISIBLE);
+        }
+
+        mMagnificationTarget = new AccessibilityButtonTarget(this, MAGNIFICATION_COMPONENT_ID,
+                R.string.accessibility_magnification_chooser_text,
+                R.drawable.resolver_icon_placeholder);
+
+        mTargets = getServiceAccessibilityButtonTargets(this);
+        if (Settings.Secure.getInt(getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
+            mTargets.add(mMagnificationTarget);
+        }
+
+        if (mTargets.size() < 2) {
+            // Why are we here?
+            finish();
+        }
+
+        GridView gridview = (GridView) findViewById(R.id.accessibility_button_chooser_grid);
+        gridview.setAdapter(new TargetAdapter());
+        gridview.setOnItemClickListener((parent, view, position, id) -> {
+            onTargetSelected(mTargets.get(position));
+        });
+    }
+
+    private static List<AccessibilityButtonTarget> getServiceAccessibilityButtonTargets(
+            @NonNull Context context) {
+        AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+        List<AccessibilityServiceInfo> services = ams.getEnabledAccessibilityServiceList(
+                AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+        if (services == null) {
+            return Collections.emptyList();
+        }
+
+        ArrayList<AccessibilityButtonTarget> targets = new ArrayList<>(services.size());
+        for (AccessibilityServiceInfo info : services) {
+            if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
+                targets.add(new AccessibilityButtonTarget(context, info));
+            }
+        }
+
+        return targets;
+    }
+
+    private void onTargetSelected(AccessibilityButtonTarget target) {
+        Settings.Secure.putString(getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, target.getId());
+        finish();
+    }
+
+    private class TargetAdapter extends BaseAdapter {
+        @Override
+        public int getCount() {
+            return mTargets.size();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return null;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            LayoutInflater inflater = AccessibilityButtonChooserActivity.this.getLayoutInflater();
+            View root = inflater.inflate(R.layout.accessibility_button_chooser_item, parent, false);
+            final AccessibilityButtonTarget target = mTargets.get(position);
+            ImageView iconView = root.findViewById(R.id.accessibility_button_target_icon);
+            TextView labelView = root.findViewById(R.id.accessibility_button_target_label);
+            iconView.setImageDrawable(target.getDrawable());
+            labelView.setText(target.getLabel());
+            return root;
+        }
+    }
+
+    private static class AccessibilityButtonTarget {
+        public String mId;
+        public CharSequence mLabel;
+        public Drawable mDrawable;
+
+        public AccessibilityButtonTarget(@NonNull Context context,
+                @NonNull AccessibilityServiceInfo serviceInfo) {
+            this.mId = serviceInfo.getComponentName().flattenToString();
+            this.mLabel = serviceInfo.getResolveInfo().loadLabel(context.getPackageManager());
+            this.mDrawable = serviceInfo.getResolveInfo().loadIcon(context.getPackageManager());
+        }
+
+        public AccessibilityButtonTarget(Context context, @NonNull String id, int labelResId,
+                int iconRes) {
+            this.mId = id;
+            this.mLabel = context.getText(labelResId);
+            this.mDrawable = context.getDrawable(iconRes);
+        }
+
+        public String getId() {
+            return mId;
+        }
+
+        public CharSequence getLabel() {
+            return mLabel;
+        }
+
+        public Drawable getDrawable() {
+            return mDrawable;
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9d2141d..3d0d6bf 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -482,7 +482,8 @@
             new StopwatchTimer[NUM_WIFI_SIGNAL_STRENGTH_BINS];
 
     int mBluetoothScanNesting;
-    StopwatchTimer mBluetoothScanTimer;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected StopwatchTimer mBluetoothScanTimer;
 
     int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
     long mMobileRadioActiveStartTime;
@@ -1589,19 +1590,28 @@
         long mStartTimeMs = -1;
 
         /**
-         * The longest time period (in ms) that the timer has been active.
+         * The longest time period (in ms) that the timer has been active. Not pooled.
          */
         long mMaxDurationMs;
 
         /**
-         * The total time (in ms) that that the timer has been active since reset().
+         * The time (in ms) that that the timer has been active since most recent
+         * stopRunningLocked() or reset(). Not pooled.
          */
         long mCurrentDurationMs;
 
+        /**
+         * The total time (in ms) that that the timer has been active since most recent reset()
+         * prior to the current startRunningLocked. This is the sum of all past currentDurations
+         * (but not including the present currentDuration) since reset. Not pooled.
+         */
+        long mTotalDurationMs;
+
         public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
                 TimeBase timeBase, Parcel in) {
             super(clocks, uid, type, timerPool, timeBase, in);
             mMaxDurationMs = in.readLong();
+            mTotalDurationMs = in.readLong();
         }
 
         public DurationTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
@@ -1613,6 +1623,7 @@
         public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
             super.writeToParcel(out, elapsedRealtimeUs);
             out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000));
+            out.writeLong(getTotalDurationMsLocked(elapsedRealtimeUs / 1000));
         }
 
         /**
@@ -1620,12 +1631,13 @@
          *
          * Since the time base is probably meaningless after we come back, reading
          * from this will have the effect of stopping the timer. So here all we write
-         * is the max duration.
+         * is the max and total durations.
          */
         @Override
         public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
             super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
             out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000));
+            out.writeLong(getTotalDurationMsLocked(elapsedRealtimeUs / 1000));
         }
 
         /**
@@ -1637,6 +1649,7 @@
         public void readSummaryFromParcelLocked(Parcel in) {
             super.readSummaryFromParcelLocked(in);
             mMaxDurationMs = in.readLong();
+            mTotalDurationMs = in.readLong();
             mStartTimeMs = -1;
             mCurrentDurationMs = 0;
         }
@@ -1692,6 +1705,7 @@
         public void stopRunningLocked(long elapsedRealtimeMs) {
             if (mNesting == 1) {
                 final long durationMs = getCurrentDurationMsLocked(elapsedRealtimeMs);
+                mTotalDurationMs += durationMs;
                 if (durationMs > mMaxDurationMs) {
                     mMaxDurationMs = durationMs;
                 }
@@ -1707,6 +1721,7 @@
         public boolean reset(boolean detachIfReset) {
             boolean result = super.reset(detachIfReset);
             mMaxDurationMs = 0;
+            mTotalDurationMs = 0;
             mCurrentDurationMs = 0;
             if (mNesting > 0) {
                 mStartTimeMs = mTimeBase.getRealtime(mClocks.elapsedRealtime()*1000) / 1000;
@@ -1735,6 +1750,7 @@
 
         /**
          * Returns the time since the timer was started.
+         * Returns 0 if the timer is not currently running.
          *
          * Note that this time is NOT split between the timers in the timer group that
          * this timer is attached to.  It is the TOTAL time.
@@ -1748,6 +1764,20 @@
             }
             return durationMs;
         }
+
+        /**
+         * Returns the total cumulative duration that this timer has been on since reset().
+         * If mTimerPool == null, this should be the same
+         * as getTotalTimeLocked(elapsedRealtimeMs*1000, STATS_SINCE_CHARGED)/1000.
+         *
+         * Note that this time is NOT split between the timers in the timer group that
+         * this timer is attached to.  It is the TOTAL time. For this reason, if mTimerPool != null,
+         * the result will not be equivalent to getTotalTimeLocked.
+         */
+        @Override
+        public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
+            return mTotalDurationMs + getCurrentDurationMsLocked(elapsedRealtimeMs);
+        }
     }
 
     /**
@@ -1972,6 +2002,116 @@
         }
     }
 
+    /**
+     * State for keeping track of two DurationTimers with different TimeBases, presumably where one
+     * TimeBase is effectively a subset of the other.
+     */
+    public static class DualTimer {
+        // mMainTimer typically tracks the total time. May be pooled (but since it's a durationTimer,
+        // it also has the unpooled getTotalDurationMsLocked() for STATS_SINCE_CHARGED).
+        private final DurationTimer mMainTimer;
+        // mSubTimer typically tracks only part of the total time, such as background time, as
+        // determined by a subTimeBase. It is NOT pooled.
+        private final DurationTimer mSubTimer;
+
+        /**
+         * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
+         * The mMainTimer is based on the given timeBase and timerPool.
+         * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
+         * the mMainTimer is.
+         */
+        public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase, TimeBase subTimeBase, Parcel in) {
+            mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase, in);
+            mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase, in);
+        }
+
+        /**
+         * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
+         * The mMainTimer is based on the given timeBase and timerPool.
+         * The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
+         * the mMainTimer is.
+         */
+        public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
+                TimeBase timeBase, TimeBase subTimeBase) {
+            mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase);
+            mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase);
+        }
+
+        /** Get the main timer. */
+        public DurationTimer getMainTimer() {
+            return mMainTimer;
+        }
+
+        /** Get the secondary timer. */
+        public DurationTimer getSubTimer() {
+            return mSubTimer;
+        }
+
+        public void startRunningLocked(long elapsedRealtimeMs) {
+            mMainTimer.startRunningLocked(elapsedRealtimeMs);
+            mSubTimer.startRunningLocked(elapsedRealtimeMs);
+        }
+
+        public void stopRunningLocked(long elapsedRealtimeMs) {
+            mMainTimer.stopRunningLocked(elapsedRealtimeMs);
+            mSubTimer.stopRunningLocked(elapsedRealtimeMs);
+        }
+
+        public void stopAllRunningLocked(long elapsedRealtimeMs) {
+            mMainTimer.stopAllRunningLocked(elapsedRealtimeMs);
+            mSubTimer.stopAllRunningLocked(elapsedRealtimeMs);
+        }
+
+        public void setMark(long elapsedRealtimeMs) {
+            mMainTimer.setMark(elapsedRealtimeMs);
+            mSubTimer.setMark(elapsedRealtimeMs);
+        }
+
+        public boolean reset(boolean detachIfReset) {
+            boolean active = false;
+            active |= !mMainTimer.reset(detachIfReset);
+            active |= !mSubTimer.reset(detachIfReset);
+            return !active;
+        }
+
+        public void detach() {
+            mMainTimer.detach();
+            mSubTimer.detach();
+        }
+
+        /**
+         * Writes a possibly null DualTimer to a Parcel.
+         *
+         * @param out the Parcel to which to write.
+         * @param t a DualTimer, or null.
+         */
+        public static void writeDualTimerToParcel(Parcel out, DualTimer t, long elapsedRealtimeUs) {
+            if (t != null) {
+                out.writeInt(1);
+                t.writeToParcel(out, elapsedRealtimeUs);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
+        public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+            mMainTimer.writeToParcel(out, elapsedRealtimeUs);
+            mSubTimer.writeToParcel(out, elapsedRealtimeUs);
+        }
+
+        public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
+            mMainTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+            mSubTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+        }
+
+        public void readSummaryFromParcelLocked(Parcel in) {
+            mMainTimer.readSummaryFromParcelLocked(in);
+            mSubTimer.readSummaryFromParcelLocked(in);
+        }
+    }
+
+
     public abstract class OverflowArrayMap<T> {
         private static final String OVERFLOW_NAME = "*overflow*";
 
@@ -3149,7 +3289,13 @@
 
     public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
             long realtime) {
-        mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
+        boolean batteryStatusChanged = mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
+
+        if (batteryStatusChanged) {
+            for (int i=0; i<mUidStats.size(); i++) {
+                mUidStats.valueAt(i).updateBgTimeBase(uptime, realtime);
+            }
+        }
 
         boolean unpluggedScreenOff = unplugged && screenOff;
         if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) {
@@ -4499,8 +4645,8 @@
 
     private void noteBluetoothScanStartedLocked(int uid) {
         uid = mapUid(uid);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        final long uptime = SystemClock.uptimeMillis();
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
         if (mBluetoothScanNesting == 0) {
             mHistoryCur.states2 |= HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
             if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan started for: "
@@ -4521,8 +4667,8 @@
 
     private void noteBluetoothScanStoppedLocked(int uid) {
         uid = mapUid(uid);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        final long uptime = SystemClock.uptimeMillis();
+        final long elapsedRealtime = mClocks.elapsedRealtime();
+        final long uptime = mClocks.uptimeMillis();
         mBluetoothScanNesting--;
         if (mBluetoothScanNesting == 0) {
             mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
@@ -4543,8 +4689,8 @@
 
     public void noteResetBluetoothScanLocked() {
         if (mBluetoothScanNesting > 0) {
-            final long elapsedRealtime = SystemClock.elapsedRealtime();
-            final long uptime = SystemClock.uptimeMillis();
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
             mBluetoothScanNesting = 0;
             mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
             if (DEBUG_HISTORY) Slog.v(TAG, "BLE can stopped for: "
@@ -5218,6 +5364,13 @@
         return true;
     }
 
+    private static boolean resetTimerIfNotNull(DualTimer timer, boolean detachIfReset) {
+        if (timer != null) {
+            return timer.reset(detachIfReset);
+        }
+        return true;
+    }
+
     private static void detachLongCounterIfNotNull(LongSamplingCounter counter) {
         if (counter != null) {
             counter.detach();
@@ -5242,6 +5395,10 @@
 
         final int mUid;
 
+        /** TimeBase for when uid is in background and device is on battery. */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public final TimeBase mOnBatteryBackgroundTimeBase;
+
         boolean mWifiRunning;
         StopwatchTimer mWifiRunningTimer;
 
@@ -5249,7 +5406,7 @@
         StopwatchTimer mFullWifiLockTimer;
 
         boolean mWifiScanStarted;
-        StopwatchTimer mWifiScanTimer;
+        DualTimer mWifiScanTimer;
 
         static final int NO_BATCHED_SCAN_STARTED = -1;
         int mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
@@ -5263,7 +5420,7 @@
         StopwatchTimer mFlashlightTurnedOnTimer;
         StopwatchTimer mCameraTurnedOnTimer;
         StopwatchTimer mForegroundActivityTimer;
-        StopwatchTimer mBluetoothScanTimer;
+        DualTimer mBluetoothScanTimer;
 
         int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
         StopwatchTimer[] mProcessStateTimer;
@@ -5357,6 +5514,10 @@
             mBsi = bsi;
             mUid = uid;
 
+            mOnBatteryBackgroundTimeBase = new TimeBase();
+            mOnBatteryBackgroundTimeBase.init(mBsi.mClocks.uptimeMillis() * 1000,
+                    mBsi.mClocks.elapsedRealtime() * 1000);
+
             mUserCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
             mSystemCpuTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
             mCpuPower = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
@@ -5383,8 +5544,8 @@
                     mBsi.mWifiRunningTimers, mBsi.mOnBatteryTimeBase);
             mFullWifiLockTimer = new StopwatchTimer(mBsi.mClocks, this, FULL_WIFI_LOCK,
                     mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase);
-            mWifiScanTimer = new StopwatchTimer(mBsi.mClocks, this, WIFI_SCAN,
-                    mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase);
+            mWifiScanTimer = new DualTimer(mBsi.mClocks, this, WIFI_SCAN,
+                    mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
             mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS];
             mWifiMulticastTimer = new StopwatchTimer(mBsi.mClocks, this, WIFI_MULTICAST_ENABLED,
                     mBsi.mWifiMulticastTimers, mBsi.mOnBatteryTimeBase);
@@ -5471,8 +5632,9 @@
             if (!mWifiScanStarted) {
                 mWifiScanStarted = true;
                 if (mWifiScanTimer == null) {
-                    mWifiScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
-                            mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase);
+                    mWifiScanTimer = new DualTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
+                            mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase,
+                            mOnBatteryBackgroundTimeBase);
                 }
                 mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
             }
@@ -5679,10 +5841,11 @@
             return mForegroundActivityTimer;
         }
 
-        public StopwatchTimer createBluetoothScanTimerLocked() {
+        public DualTimer createBluetoothScanTimerLocked() {
             if (mBluetoothScanTimer == null) {
-                mBluetoothScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
-                        mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase);
+                mBluetoothScanTimer = new DualTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
+                        mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase,
+                        mOnBatteryBackgroundTimeBase);
             }
             return mBluetoothScanTimer;
         }
@@ -5755,7 +5918,7 @@
             if (mWifiScanTimer == null) {
                 return 0;
             }
-            return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+            return mWifiScanTimer.getMainTimer().getTotalTimeLocked(elapsedRealtimeUs, which);
         }
 
         @Override
@@ -5763,7 +5926,33 @@
             if (mWifiScanTimer == null) {
                 return 0;
             }
-            return mWifiScanTimer.getCountLocked(which);
+            return mWifiScanTimer.getMainTimer().getCountLocked(which);
+        }
+
+        @Override
+        public int getWifiScanBackgroundCount(int which) {
+            if (mWifiScanTimer == null) {
+                return 0;
+            }
+            return mWifiScanTimer.getSubTimer().getCountLocked(which);
+        }
+
+        @Override
+        public long getWifiScanActualTime(final long elapsedRealtimeUs) {
+            if (mWifiScanTimer == null) {
+                return 0;
+            }
+            final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
+            return mWifiScanTimer.getMainTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
+        }
+
+        @Override
+        public long getWifiScanBackgroundTime(final long elapsedRealtimeUs) {
+            if (mWifiScanTimer == null) {
+                return 0;
+            }
+            final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
+            return mWifiScanTimer.getSubTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
         }
 
         @Override
@@ -5819,7 +6008,18 @@
 
         @Override
         public Timer getBluetoothScanTimer() {
-            return mBluetoothScanTimer;
+            if (mBluetoothScanTimer == null) {
+                return null;
+            }
+            return mBluetoothScanTimer.getMainTimer();
+        }
+
+        @Override
+        public Timer getBluetoothScanBackgroundTimer() {
+            if (mBluetoothScanTimer == null) {
+                return null;
+            }
+            return mBluetoothScanTimer.getSubTimer();
         }
 
         void makeProcessState(int i, Parcel in) {
@@ -6216,6 +6416,9 @@
             mLastStepUserTime = mLastStepSystemTime = 0;
             mCurStepUserTime = mCurStepSystemTime = 0;
 
+            mOnBatteryBackgroundTimeBase.reset(mBsi.mClocks.elapsedRealtime() * 1000,
+                    mBsi.mClocks.uptimeMillis() * 1000);
+
             if (!active) {
                 if (mWifiRunningTimer != null) {
                     mWifiRunningTimer.detach();
@@ -6307,7 +6510,9 @@
             return !active;
         }
 
-        void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
+        void writeToParcelLocked(Parcel out, long uptimeUs, long elapsedRealtimeUs) {
+            mOnBatteryBackgroundTimeBase.writeToParcel(out, uptimeUs, elapsedRealtimeUs);
+
             final ArrayMap<String, Wakelock> wakeStats = mWakelockStats.getMap();
             int NW = wakeStats.size();
             out.writeInt(NW);
@@ -6525,6 +6730,8 @@
         }
 
         void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
+            mOnBatteryBackgroundTimeBase.readFromParcel(in);
+
             int numWakelocks = in.readInt();
             mWakelockStats.clear();
             for (int j = 0; j < numWakelocks; j++) {
@@ -6559,7 +6766,8 @@
             for (int k = 0; k < numSensors; k++) {
                 int sensorNumber = in.readInt();
                 Uid.Sensor sensor = new Sensor(mBsi, this, sensorNumber);
-                sensor.readFromParcelLocked(mBsi.mOnBatteryTimeBase, in);
+                sensor.readFromParcelLocked(mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase,
+                        in);
                 mSensorStats.put(sensorNumber, sensor);
             }
 
@@ -6597,8 +6805,9 @@
             }
             mWifiScanStarted = false;
             if (in.readInt() != 0) {
-                mWifiScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
-                        mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, in);
+                mWifiScanTimer = new DualTimer(mBsi.mClocks, Uid.this, WIFI_SCAN,
+                        mBsi.mWifiScanTimers, mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase,
+                        in);
             } else {
                 mWifiScanTimer = null;
             }
@@ -6648,8 +6857,9 @@
                 mForegroundActivityTimer = null;
             }
             if (in.readInt() != 0) {
-                mBluetoothScanTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
-                        mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase, in);
+                mBluetoothScanTimer = new DualTimer(mBsi.mClocks, Uid.this, BLUETOOTH_SCAN_ON,
+                        mBsi.mBluetoothScanOnTimers, mBsi.mOnBatteryTimeBase,
+                        mOnBatteryBackgroundTimeBase, in);
             } else {
                 mBluetoothScanTimer = null;
             }
@@ -6946,14 +7156,12 @@
             protected BatteryStatsImpl mBsi;
 
             /**
-             * BatteryStatsImpl that we are associated with.
+             * Uid that we are associated with.
              */
             protected Uid mUid;
 
             final int mHandle;
-            StopwatchTimer mTimer;
-
-            Counter mBgCounter;
+            DualTimer mTimer;
 
             public Sensor(BatteryStatsImpl bsi, Uid uid, int handle) {
                 mBsi = bsi;
@@ -6961,7 +7169,8 @@
                 mHandle = handle;
             }
 
-            private StopwatchTimer readTimerFromParcel(TimeBase timeBase, Parcel in) {
+            private DualTimer readTimersFromParcel(
+                    TimeBase timeBase, TimeBase bgTimeBase, Parcel in) {
                 if (in.readInt() == 0) {
                     return null;
                 }
@@ -6971,23 +7180,10 @@
                     pool = new ArrayList<StopwatchTimer>();
                     mBsi.mSensorTimers.put(mHandle, pool);
                 }
-                return new StopwatchTimer(mBsi.mClocks, mUid, 0, pool, timeBase, in);
-            }
-
-            private Counter readCounterFromParcel(TimeBase timeBase, Parcel in) {
-                if (in.readInt() == 0) {
-                    return null;
-                }
-                return new Counter(timeBase, in);
+                return new DualTimer(mBsi.mClocks, mUid, 0, pool, timeBase, bgTimeBase, in);
             }
 
             boolean reset() {
-                if (mBgCounter != null) {
-                    mBgCounter.reset(true /*detachIfReset*/);
-                    // If we detach, we must null the mBgCounter reference so that it
-                    // can be recreated and attached.
-                    mBgCounter = null;
-                }
                 if (mTimer.reset(true)) {
                     mTimer = null;
                     return true;
@@ -6995,24 +7191,28 @@
                 return false;
             }
 
-            void readFromParcelLocked(TimeBase timeBase, Parcel in) {
-                mTimer = readTimerFromParcel(timeBase, in);
-                mBgCounter = readCounterFromParcel(timeBase, in);
+            void readFromParcelLocked(TimeBase timeBase, TimeBase bgTimeBase, Parcel in) {
+                mTimer = readTimersFromParcel(timeBase, bgTimeBase, in);
             }
 
             void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
-                Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs);
-                Counter.writeCounterToParcel(out, mBgCounter);
+                DualTimer.writeDualTimerToParcel(out, mTimer, elapsedRealtimeUs);
             }
 
             @Override
             public Timer getSensorTime() {
-                return mTimer;
+                if (mTimer == null) {
+                    return null;
+                }
+                return mTimer.getMainTimer();
             }
 
             @Override
-            public Counter getSensorBgCount() {
-                return mBgCounter;
+            public Timer getSensorBackgroundTime() {
+                if (mTimer == null) {
+                    return null;
+                }
+                return mTimer.getSubTimer();
             }
 
             @Override
@@ -7749,18 +7949,29 @@
 
             if (mProcessState == uidRunningState) return;
 
-            final long elapsedRealtime = mBsi.mClocks.elapsedRealtime();
+            final long elapsedRealtimeMs = mBsi.mClocks.elapsedRealtime();
+            final long uptimeMs = mBsi.mClocks.uptimeMillis();
 
             if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
-                mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtime);
+                mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
             }
             mProcessState = uidRunningState;
             if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
                 if (mProcessStateTimer[uidRunningState] == null) {
                     makeProcessState(uidRunningState, null);
                 }
-                mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtime);
+                mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
             }
+
+            updateBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
+        }
+
+        public boolean updateBgTimeBase(long uptimeUs, long realtimeUs) {
+            // Note that PROCESS_STATE_CACHED and ActivityManager.PROCESS_STATE_NONEXISTENT is
+            // also considered to be 'background' for our purposes, because it's not foreground.
+            boolean isBgAndUnplugged = mBsi.mOnBatteryTimeBase.isRunning()
+                    && mProcessState >= PROCESS_STATE_BACKGROUND;
+            return mOnBatteryBackgroundTimeBase.setRunning(isBgAndUnplugged, uptimeUs, realtimeUs);
         }
 
         public SparseArray<? extends Pid> getPidStats() {
@@ -7834,7 +8045,7 @@
             }
         }
 
-        public StopwatchTimer getSensorTimerLocked(int sensor, boolean create) {
+        public DualTimer getSensorTimerLocked(int sensor, boolean create) {
             Sensor se = mSensorStats.get(sensor);
             if (se == null) {
                 if (!create) {
@@ -7843,7 +8054,7 @@
                 se = new Sensor(mBsi, this, sensor);
                 mSensorStats.put(sensor, se);
             }
-            StopwatchTimer t = se.mTimer;
+            DualTimer t = se.mTimer;
             if (t != null) {
                 return t;
             }
@@ -7852,28 +8063,12 @@
                 timers = new ArrayList<StopwatchTimer>();
                 mBsi.mSensorTimers.put(sensor, timers);
             }
-            t = new StopwatchTimer(mBsi.mClocks, this, BatteryStats.SENSOR, timers,
-                    mBsi.mOnBatteryTimeBase);
+            t = new DualTimer(mBsi.mClocks, this, BatteryStats.SENSOR, timers,
+                    mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
             se.mTimer = t;
             return t;
         }
 
-        public Counter getSensorBgCounterLocked(int sensor, boolean create) {
-            Sensor se = mSensorStats.get(sensor);
-            if (se == null) {
-                if (!create) {
-                    return null;
-                }
-                se = new Sensor(mBsi, this, sensor);
-                mSensorStats.put(sensor, se);
-            }
-            Counter c = se.mBgCounter;
-            if (c != null) return c;
-            c = new Counter(mBsi.mOnBatteryTimeBase);
-            se.mBgCounter = c;
-            return c;
-        }
-
         public void noteStartSyncLocked(String name, long elapsedRealtimeMs) {
             StopwatchTimer t = mSyncStats.startObject(name);
             if (t != null) {
@@ -7946,39 +8141,24 @@
         }
 
         public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
-            StopwatchTimer t = getSensorTimerLocked(sensor, /* create= */ true);
+            DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
             t.startRunningLocked(elapsedRealtimeMs);
-
-            Counter c = getSensorBgCounterLocked(sensor, /* create= */ true);
-            if (mProcessState >= PROCESS_STATE_BACKGROUND && t.mNesting == 1) {
-                c.stepAtomic();
-            }
         }
 
         public void noteStopSensor(int sensor, long elapsedRealtimeMs) {
             // Don't create a timer if one doesn't already exist
-            StopwatchTimer t = getSensorTimerLocked(sensor, false);
+            DualTimer t = getSensorTimerLocked(sensor, false);
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
             }
         }
 
         public void noteStartGps(long elapsedRealtimeMs) {
-            StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, /* create= */ true);
-            t.startRunningLocked(elapsedRealtimeMs);
-
-            Counter c = getSensorBgCounterLocked(Sensor.GPS, /* create= */ true);
-            if (mProcessState >= PROCESS_STATE_BACKGROUND && t.mNesting == 1) {
-                c.stepAtomic();
-            }
+            noteStartSensor(Sensor.GPS, elapsedRealtimeMs);
         }
 
         public void noteStopGps(long elapsedRealtimeMs) {
-            // Don't create a timer if one doesn't already exist
-            StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false);
-            if (t != null) {
-                t.stopRunningLocked(elapsedRealtimeMs);
-            }
+            noteStopSensor(Sensor.GPS, elapsedRealtimeMs);
         }
 
         public BatteryStatsImpl getBatteryStats() {
@@ -8969,7 +9149,7 @@
                 final Uid uid = mUidStats.valueAt(i);
 
                 // Sum the total scan power for all apps.
-                totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
+                totalScanTimeMs += uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
                         elapsedRealtimeMs * 1000) / 1000;
 
                 // Sum the total time holding wifi lock for all apps.
@@ -8990,7 +9170,7 @@
             for (int i = 0; i < uidStatsSize; i++) {
                 final Uid uid = mUidStats.valueAt(i);
 
-                long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
+                long scanTimeSinceMarkMs = uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
                         elapsedRealtimeMs * 1000) / 1000;
                 if (scanTimeSinceMarkMs > 0) {
                     // Set the new mark so that next time we get new data since this point.
@@ -9244,7 +9424,7 @@
 
         mHasBluetoothReporting = true;
 
-        final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+        final long elapsedRealtimeMs = mClocks.elapsedRealtime();
         final long rxTimeMs = info.getControllerRxTimeMillis();
         final long txTimeMs = info.getControllerTxTimeMillis();
 
@@ -9264,7 +9444,7 @@
                 continue;
             }
 
-            totalScanTimeMs += u.mBluetoothScanTimer.getTimeSinceMarkLocked(
+            totalScanTimeMs += u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
                     elapsedRealtimeMs * 1000) / 1000;
         }
 
@@ -9285,7 +9465,7 @@
                 continue;
             }
 
-            long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getTimeSinceMarkLocked(
+            long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
                     elapsedRealtimeMs * 1000) / 1000;
             if (scanTimeSinceMarkMs > 0) {
                 // Set the new mark so that next time we get new data since this point.
@@ -10791,6 +10971,8 @@
             Uid u = new Uid(this, uid);
             mUidStats.put(uid, u);
 
+            u.mOnBatteryBackgroundTimeBase.readSummaryFromParcel(in);
+
             u.mWifiRunning = false;
             if (in.readInt() != 0) {
                 u.mWifiRunningTimer.readSummaryFromParcelLocked(in);
@@ -10948,8 +11130,7 @@
             for (int is = 0; is < NP; is++) {
                 int seNumber = in.readInt();
                 if (in.readInt() != 0) {
-                    u.getSensorTimerLocked(seNumber, true)
-                            .readSummaryFromParcelLocked(in);
+                    u.getSensorTimerLocked(seNumber, true).readSummaryFromParcelLocked(in);
                 }
             }
 
@@ -11155,6 +11336,8 @@
             out.writeInt(mUidStats.keyAt(iu));
             Uid u = mUidStats.valueAt(iu);
 
+            u.mOnBatteryBackgroundTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
+
             if (u.mWifiRunningTimer != null) {
                 out.writeInt(1);
                 u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
@@ -11738,7 +11921,7 @@
                 out.writeInt(mUidStats.keyAt(i));
                 Uid uid = mUidStats.valueAt(i);
 
-                uid.writeToParcelLocked(out, uSecRealtime);
+                uid.writeToParcelLocked(out, uSecUptime, uSecRealtime);
             }
         } else {
             out.writeInt(0);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 373bda9..90aee9e 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -178,11 +178,7 @@
                 // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
                 // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
                 char subdir[PROP_VALUE_MAX];
-                int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PERSIST_PROPERTY,
-                        subdir);
-                if (len == 0) {
-                    len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
-                }
+                int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
                 if (len > 0) {
                     String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir;
                     if (stat(overlayPath.string(), &st) == 0) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8abd022..8721f34 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3344,6 +3344,19 @@
                 <category android:name="android.intent.category.VOICE" />
             </intent-filter>
         </activity>
+        <activity android:name="com.android.internal.app.AccessibilityButtonChooserActivity"
+                  android:theme="@style/Theme.DeviceDefault.Resolver"
+                  android:finishOnCloseSystemDialogs="true"
+                  android:excludeFromRecents="true"
+                  android:documentLaunchMode="never"
+                  android:relinquishTaskIdentity="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                  android:process=":ui">
+            <intent-filter>
+                <action android:name="android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <activity android:name="com.android.internal.app.IntentForwarderActivity"
                 android:finishOnCloseSystemDialogs="true"
                 android:theme="@style/Theme.NoDisplay"
diff --git a/core/res/res/layout/accessibility_button_chooser.xml b/core/res/res/layout/accessibility_button_chooser.xml
new file mode 100644
index 0000000..0ef785f
--- /dev/null
+++ b/core/res/res/layout/accessibility_button_chooser.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2017, The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+<com.android.internal.widget.ResolverDrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:maxWidth="@dimen/resolver_max_width"
+    android:maxCollapsedHeight="256dp"
+    android:maxCollapsedHeightSmall="56dp"
+    android:id="@id/contentPanel">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:background="?attr/colorBackground"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:minHeight="56dp"
+            android:textAppearance="?attr/textAppearanceMedium"
+            android:text="@string/accessibility_button_prompt_text"
+            android:gravity="start|center_vertical"
+            android:layout_alignParentStart="true"
+            android:paddingStart="?attr/dialogPreferredPadding"
+            android:paddingEnd="?attr/dialogPreferredPadding"
+            android:paddingTop="8dp"
+            android:paddingBottom="8dp"/>
+
+        <GridView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/accessibility_button_chooser_grid"
+            android:columnWidth="90dp"
+            android:numColumns="auto_fit"
+            android:verticalSpacing="10dp"
+            android:horizontalSpacing="10dp"
+            android:stretchMode="columnWidth"
+            android:paddingStart="?attr/dialogPreferredPadding"
+            android:paddingEnd="?attr/dialogPreferredPadding"
+            android:gravity="center"/>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/accessibility_button_prompt"
+            android:layout_alwaysShow="true"
+            android:textAppearance="?attr/textAppearanceMedium"
+            android:text="@string/accessibility_button_instructional_text"
+            android:gravity="start|center_vertical"
+            android:paddingStart="?attr/dialogPreferredPadding"
+            android:paddingEnd="?attr/dialogPreferredPadding"
+            android:paddingTop="8dp"
+            android:paddingBottom="8dp"
+            android:visibility="gone"/>
+    </LinearLayout>
+</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml
new file mode 100644
index 0000000..76a9308
--- /dev/null
+++ b/core/res/res/layout/accessibility_button_chooser_item.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:minWidth="80dp"
+              android:gravity="center"
+              android:paddingTop="8dp"
+              android:paddingBottom="8dp"
+              android:background="?attr/selectableItemBackgroundBorderless">
+
+    <ImageView android:id="@+id/accessibility_button_target_icon"
+               android:layout_width="48dp"
+               android:layout_height="48dp"
+               android:layout_marginLeft="3dp"
+               android:layout_marginRight="3dp"
+               android:layout_marginBottom="3dp"
+               android:scaleType="fitCenter"/>
+
+    <TextView android:id="@+id/accessibility_button_target_label"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_marginTop="8dp"
+              android:layout_marginLeft="4dp"
+              android:layout_marginRight="4dp"
+              android:textAppearance="?attr/textAppearanceSmall"
+              android:textColor="?attr/textColorPrimary"
+              android:textSize="12sp"
+              android:fontFamily="sans-serif-condensed"
+              android:gravity="top|center_horizontal"
+              android:minLines="2"
+              android:maxLines="2"
+              android:ellipsize="marquee"/>
+</LinearLayout>
+
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b9409f2..ed5a42b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1191,6 +1191,20 @@
          <p>The default value is <code>false</code>.  -->
     <attr name="supportsPictureInPicture" format="boolean" />
 
+    <!-- This value indicates the maximum aspect ratio the activity supports. If the app runs on a
+         device with a wider aspect ratio, the system automatically letterboxes the app, leaving
+         portions of the screen unused so the app can run at its specified maximum aspect ratio.
+         <p>
+         Maximum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal
+         form. For example, if the maximum aspect ratio is 7:3, set value to 2.33.
+         <p>
+         Value needs to be greater or equal to 1.0, otherwise it is ignored.
+         <p>
+         NOTE: This attribute is ignored if the activity has
+         {@link android.R.attr#resizeableActivity} set to true, since that means your activity
+         supports any size. -->
+    <attr name="maxAspectRatio" format="float" />
+
     <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
          While in lockTask mode the system will not launch non-permitted tasks until
          lockTask mode is disabled.
@@ -1408,6 +1422,7 @@
         <attr name="defaultToDeviceProtectedStorage" format="boolean" />
         <attr name="directBootAware" />
         <attr name="resizeableActivity" />
+        <attr name="maxAspectRatio" />
         <attr name="networkSecurityConfig" />
         <!-- Declare the category of this app. Categories are used to cluster multiple apps
              together into meaningful groups, such as when summarizing battery, network, or
@@ -2066,6 +2081,7 @@
         <attr name="resumeWhilePausing" />
         <attr name="resizeableActivity" />
         <attr name="supportsPictureInPicture" />
+        <attr name="maxAspectRatio" />
         <attr name="lockTaskMode" />
         <attr name="showForAllUsers" />
         <attr name="directBootAware" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d6b5527..876d44d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2812,6 +2812,7 @@
         <public name="fontProviderCerts" />
         <public name="iconTint" />
         <public name="iconTintMode" />
+        <public name="maxAspectRatio"/>
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9d2c6a7..868e256 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3929,22 +3929,24 @@
 
     <!-- Dialog title for dialog shown when the accessibility shortcut is activated, and we want
      to confirm that the user understands what's going to happen-->
-    <string name="accessibility_shortcut_warning_dialog_title">Accessibility Shortcut is ON</string>
+    <string name="accessibility_shortcut_warning_dialog_title">Use Accessibility Shortcut?</string>
 
     <!-- Message shown in dialog when user is in the process of enabling the accessibility
     service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
     <string name="accessibility_shortcut_toogle_warning">
-        Turn <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> on or off by holding down
-        both volume buttons for 3 seconds.\n\nYou can change the service in
-        Settings > Accessibility.
+        When the shortcut is on, pressing both volume buttons for 3 seconds will start an
+        accessibility feature.\n\n
+        Current accessibility feature:\n
+        <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>\n\n
+        You can change the feature in Settings > Accessibility.
     </string>
 
     <!-- Text in button that turns off the accessibility shortcut -->
-    <string name="disable_accessibility_shortcut">Turn Off Shortcut</string>
+    <string name="disable_accessibility_shortcut">Turn off Shortcut</string>
 
     <!-- Text in button that closes the warning dialog about the accessibility shortcut, leaving the
     shortcut enabled.-->
-    <string name="leave_accessibility_shortcut_on">Leave on</string>
+    <string name="leave_accessibility_shortcut_on">Use Shortcut</string>
 
     <!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility
     service.-->
@@ -3956,6 +3958,15 @@
     <string name="accessibility_shortcut_disabling_service">Accessibility Shortcut turned
         <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> off</string>
 
+    <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. -->
+    <string name="accessibility_button_prompt_text">Choose a feature to use when you tap the Accessibility button:</string>
+
+    <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. -->
+    <string name="accessibility_button_instructional_text">To change features, touch &amp; hold the Accessibility button.</string>
+
+    <!-- Text used to describe system navigation features, shown within a UI allowing a user to assign system magnification features to the Accessibility button in the navigation bar. -->
+    <string name="accessibility_magnification_chooser_text">Magnification</string>
+
     <!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] -->
     <string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
     <!-- Message shown when switching to a user [CHAR LIMIT=none] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f4d490a..b23c96c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2841,6 +2841,15 @@
   <java-symbol type="string" name="leave_accessibility_shortcut_on" />
   <java-symbol type="string" name="config_defaultAccessibilityService" />
 
+  <!-- Accessibility Button -->
+  <java-symbol type="layout" name="accessibility_button_chooser" />
+  <java-symbol type="layout" name="accessibility_button_chooser_item" />
+  <java-symbol type="id" name="accessibility_button_chooser_grid" />
+  <java-symbol type="id" name="accessibility_button_prompt" />
+  <java-symbol type="id" name="accessibility_button_target_icon" />
+  <java-symbol type="id" name="accessibility_button_target_label" />
+  <java-symbol type="string" name="accessibility_magnification_chooser_text" />
+
   <!-- com.android.internal.widget.RecyclerView -->
   <java-symbol type="id" name="item_touch_helper_previous_elevation"/>
   <java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 7deff06..9911d9d 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -790,6 +790,8 @@
         <item name="colorAccent">@color/accent_device_default_light</item>
     </style>
 
+    <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar"/>
+
     <!-- Theme used for the intent picker activity. -->
     <style name="Theme.DeviceDefault.Resolver" parent="Theme.Material.Light">
         <item name="windowIsTranslucent">true</item>
diff --git a/core/tests/coretests/README b/core/tests/coretests/README
index 4a69843..aced441 100644
--- a/core/tests/coretests/README
+++ b/core/tests/coretests/README
@@ -45,6 +45,10 @@
 
     -e debug true
 
+To uninstall the package:
+
+  adb shell pm uninstall -k com.android.frameworks.coretests
+
 For more arguments, see the guide to command=line testing:
 
   https://developer.android.com/studio/test/command-line.html
diff --git a/core/tests/coretests/src/android/content/ContentTests.java b/core/tests/coretests/src/android/content/ContentTests.java
index a1299e3..567b79a 100644
--- a/core/tests/coretests/src/android/content/ContentTests.java
+++ b/core/tests/coretests/src/android/content/ContentTests.java
@@ -23,6 +23,7 @@
         TestSuite suite = new TestSuite(ContentTests.class.getName());
 
         suite.addTestSuite(AssetTest.class);
+        suite.addTestSuite(ContentValuesTest.class);
         return suite;
     }
 }
diff --git a/core/tests/coretests/src/android/content/ContentValuesTest.java b/core/tests/coretests/src/android/content/ContentValuesTest.java
new file mode 100644
index 0000000..7b39939
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ContentValuesTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+
+/*
+  runtest -c android.content.ContentValuesTest frameworks-core
+
+  or
+
+  make -j256 FrameworksCoreTests && \
+    adb shell pm uninstall -k com.android.frameworks.coretests && \
+    adb install out/target/product/bullhead/testcases/FrameworksCoreTests/FrameworksCoreTests.apk && \
+    adb shell am instrument -w -e package android.content \
+      com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+*/
+public class ContentValuesTest extends AndroidTestCase {
+
+    @SmallTest
+    public void testIsEmpty() throws Exception {
+        ContentValues cv = new ContentValues();
+        assertTrue(cv.isEmpty());
+        assertEquals(0, cv.size());
+
+        cv.put("key", "value");
+        assertFalse(cv.isEmpty());
+        assertEquals(1, cv.size());
+    }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 3e33dd8..782a50f 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -328,6 +328,7 @@
                     Settings.Global.USE_GOOGLE_MAIL,
                     Settings.Global.VT_IMS_ENABLED,
                     Settings.Global.WAIT_FOR_DEBUGGER,
+                    Settings.Global.NETWORK_ACCESS_TIMEOUT_MS,
                     Settings.Global.WARNING_TEMPERATURE,
                     Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
                     Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
new file mode 100644
index 0000000..6b52b98
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.internal.os;
+
+import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+
+import android.app.ActivityManager;
+import android.os.BatteryStats;
+import android.os.WorkSource;
+import android.support.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Test BatteryStatsImpl onBatteryBackgroundTimeBase TimeBase.
+ */
+public class BatteryStatsBackgroundStatsTest extends TestCase {
+
+    private static final int UID = 10500;
+
+    /** Test that BatteryStatsImpl.Uid.mOnBatteryBackgroundTimeBase works correctly. */
+    @SmallTest
+    public void testBgTimeBase() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        long cur = 0; // realtime in us
+
+        BatteryStatsImpl.TimeBase bgtb = bi.getOnBatteryBackgroundTimeBase(UID);
+
+        // Off-battery, non-existent
+        clocks.realtime = clocks.uptime = 10;
+        cur = clocks.realtime * 1000;
+        bi.updateTimeBasesLocked(false, false, cur, cur); // off battery
+        assertFalse(bgtb.isRunning());
+        assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+        // Off-battery, foreground
+        clocks.realtime = clocks.uptime = 100;
+        cur = clocks.realtime * 1000;
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+        assertFalse(bgtb.isRunning());
+        assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+        // Off-battery, background
+        clocks.realtime = clocks.uptime = 201;
+        cur = clocks.realtime * 1000;
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+        assertFalse(bgtb.isRunning());
+        assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+        // On-battery, background
+        clocks.realtime = clocks.uptime = 303;
+        cur = clocks.realtime * 1000;
+        bi.updateTimeBasesLocked(true, false, cur, cur); // on battery
+        // still in ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+        assertTrue(bgtb.isRunning());
+        assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+        // On-battery, background - but change screen state
+        clocks.realtime = clocks.uptime = 409;
+        cur = clocks.realtime * 1000;
+        bi.updateTimeBasesLocked(true, true, cur, cur); // on battery (again)
+        assertTrue(bgtb.isRunning());
+        assertEquals(106_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+        // On-battery, background - but a different background state
+        clocks.realtime = clocks.uptime = 417;
+        cur = clocks.realtime * 1000;
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_RECEIVER); // background too
+        assertTrue(bgtb.isRunning());
+        assertEquals(114_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+        // Off-battery, foreground
+        clocks.realtime = clocks.uptime = 530;
+        cur = clocks.realtime * 1000;
+        bi.updateTimeBasesLocked(false, false, cur, cur); // off battery
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+        assertFalse(bgtb.isRunning());
+        assertEquals(227_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+
+        // Off-battery, non-existent
+        clocks.realtime = clocks.uptime = 690;
+        cur = clocks.realtime * 1000;
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_NONEXISTENT);
+        assertFalse(bgtb.isRunning());
+        assertEquals(227_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
+    }
+
+    @SmallTest
+    public void testWifiScan() throws Exception {
+        final MockClocks clocks = new MockClocks();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        long curr = 0; // realtime in us
+
+        // On battery
+        curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+        bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+        // App in foreground
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+        // Start timer
+        curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+        bi.noteWifiScanStartedLocked(UID);
+
+        // Move to background
+        curr = 1000 * (clocks.realtime = clocks.uptime = 254);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+        // Off battery
+        curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+        bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+
+        // Stop timer
+        curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+        bi.noteWifiScanStoppedLocked(UID);
+
+        // Test
+        curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+        long time = bi.getUidStats().get(UID).getWifiScanTime(curr, STATS_SINCE_CHARGED);
+        int count = bi.getUidStats().get(UID).getWifiScanCount(STATS_SINCE_CHARGED);
+        int bgCount = bi.getUidStats().get(UID).getWifiScanBackgroundCount(STATS_SINCE_CHARGED);
+        long actualTime = bi.getUidStats().get(UID).getWifiScanActualTime(curr);
+        long bgTime = bi.getUidStats().get(UID).getWifiScanBackgroundTime(curr);
+        assertEquals((305 - 202) * 1000, time);
+        assertEquals(1, count);
+        assertEquals(1, bgCount);
+        assertEquals((305 - 202) * 1000, actualTime);
+        assertEquals((305 - 254) * 1000, bgTime);
+    }
+
+    @SmallTest
+    public void testAppBluetoothScan() throws Exception {
+        final MockClocks clocks = new MockClocks();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        WorkSource ws = new WorkSource(UID); // needed for bluetooth
+        long curr = 0; // realtime in us
+
+        // On battery
+        curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+        bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+
+        // App in foreground
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+        // Start timer
+        curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+        bi.noteBluetoothScanStartedFromSourceLocked(ws);
+
+        // Move to background
+        curr = 1000 * (clocks.realtime = clocks.uptime = 254);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+        // Off battery
+        curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+        bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+
+        // Stop timer
+        curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+        bi.noteBluetoothScanStoppedFromSourceLocked(ws);
+
+        // Test
+        curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+        BatteryStats.Timer timer = bi.getUidStats().get(UID).getBluetoothScanTimer();
+        BatteryStats.Timer bgTimer = bi.getUidStats().get(UID).getBluetoothScanBackgroundTimer();
+
+        long time = timer.getTotalTimeLocked(curr, STATS_SINCE_CHARGED);
+        int count = timer.getCountLocked(STATS_SINCE_CHARGED);
+        int bgCount = bgTimer.getCountLocked(STATS_SINCE_CHARGED);
+        long actualTime = timer.getTotalDurationMsLocked(clocks.realtime) * 1000;
+        long bgTime = bgTimer.getTotalDurationMsLocked(clocks.realtime) * 1000;
+        assertEquals((305 - 202) * 1000, time);
+        assertEquals(1, count);
+        assertEquals(1, bgCount);
+        assertEquals((305 - 202) * 1000, actualTime);
+        assertEquals((305 - 254) * 1000, bgTime);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
index f1aeecc..a1b05cd 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java
@@ -44,18 +44,21 @@
         assertFalse(timer.isRunningLocked());
         assertEquals(0, timer.getCurrentDurationMsLocked(300));
         assertEquals(0, timer.getMaxDurationMsLocked(301));
+        assertEquals(0, timer.getTotalDurationMsLocked(301));
 
-        // Start timer: current and max advance
+        // Start timer: current, total, and max advance
         timer.startRunningLocked(700);
         assertTrue(timer.isRunningLocked());
         assertEquals(800, timer.getCurrentDurationMsLocked(1500));
         assertEquals(801, timer.getMaxDurationMsLocked(1501));
+        assertEquals(802, timer.getTotalDurationMsLocked(1502));
 
-        // Stop timer: current resets to 0, max remains
+        // Stop timer: current resets to 0; total and max remain
         timer.stopRunningLocked(3100);
         assertFalse(timer.isRunningLocked());
         assertEquals(0, timer.getCurrentDurationMsLocked(6300));
         assertEquals(2400, timer.getMaxDurationMsLocked(6301));
+        assertEquals(2400, timer.getTotalDurationMsLocked(6302));
 
         // Start time again, but check with a short time, and make sure max doesn't
         // increment.
@@ -63,31 +66,36 @@
         assertTrue(timer.isRunningLocked());
         assertEquals(100, timer.getCurrentDurationMsLocked(12800));
         assertEquals(2400, timer.getMaxDurationMsLocked(12801));
+        assertEquals(2502, timer.getTotalDurationMsLocked(12802));
 
         // And stop it again, but with a short time, and make sure it doesn't increment.
         timer.stopRunningLocked(12900);
         assertFalse(timer.isRunningLocked());
         assertEquals(0, timer.getCurrentDurationMsLocked(13000));
         assertEquals(2400, timer.getMaxDurationMsLocked(13001));
+        assertEquals(2600, timer.getTotalDurationMsLocked(13002));
 
         // Now start and check that the time doesn't increase if the two times are the same.
         timer.startRunningLocked(27000);
         assertTrue(timer.isRunningLocked());
         assertEquals(0, timer.getCurrentDurationMsLocked(27000));
         assertEquals(2400, timer.getMaxDurationMsLocked(27000));
+        assertEquals(2600, timer.getTotalDurationMsLocked(27000));
 
         // Stop the TimeBase. The values should be frozen.
         timeBase.setRunning(false, /* uptimeUs */ 10, /* realtimeUs */ 55000*1000);
         assertTrue(timer.isRunningLocked());
         assertEquals(28000, timer.getCurrentDurationMsLocked(110100));
         assertEquals(28000, timer.getMaxDurationMsLocked(110101));
+        assertEquals(30600, timer.getTotalDurationMsLocked(110102));
 
         // Start the TimeBase. The values should be the old value plus the delta
-        // between when the timer restarted and the current time
+        // between when the timer restarted and the current time.
         timeBase.setRunning(true, /* uptimeUs */ 10, /* realtimeUs */ 220100*1000);
         assertTrue(timer.isRunningLocked());
         assertEquals(28200, timer.getCurrentDurationMsLocked(220300));
         assertEquals(28201, timer.getMaxDurationMsLocked(220301));
+        assertEquals(30802, timer.getTotalDurationMsLocked(220302));
     }
 
     @SmallTest
@@ -114,6 +122,7 @@
         // Check that it did start running
         assertEquals(400, timer.getMaxDurationMsLocked(700));
         assertEquals(401, timer.getCurrentDurationMsLocked(701));
+        assertEquals(402, timer.getTotalDurationMsLocked(702));
 
         // Write summary
         final Parcel summaryParcel = Parcel.obtain();
@@ -128,8 +137,9 @@
         // The new one shouldn't be running, and therefore 0 for current time
         assertFalse(summary.isRunningLocked());
         assertEquals(0, summary.getCurrentDurationMsLocked(6300));
-        // The new one should have the max duration that we had when we wrote it
+        // The new one should have the max and total durations that we had when we wrote it
         assertEquals(1200, summary.getMaxDurationMsLocked(6301));
+        assertEquals(1200, summary.getTotalDurationMsLocked(6302));
 
         // Write full
         final Parcel fullParcel = Parcel.obtain();
@@ -142,7 +152,9 @@
         // The new one shouldn't be running, and therefore 0 for current time
         assertFalse(full.isRunningLocked());
         assertEquals(0, full.getCurrentDurationMsLocked(6300));
-        // The new one should have the max duration that we had when we wrote it
+        // The new one should have the max and total durations that we had when we wrote it
         assertEquals(1200, full.getMaxDurationMsLocked(6301));
+        assertEquals(1200, full.getTotalDurationMsLocked(6302));
+
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
index b4afdda..251ceb0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
@@ -146,6 +146,8 @@
         BatteryStatsImpl.SamplingTimer timer = new BatteryStatsImpl.SamplingTimer(clocks, timeBase);
 
         // Start running on battery.
+        // (Note that the wrong units are used in this class. setRunning is actually supposed to
+        // take us, not the ms that clocks uses.)
         timeBase.setRunning(true, clocks.uptimeMillis(), clocks.elapsedRealtime());
 
         // The first update on battery consumes the values as a way of starting cleanly.
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index 4ec78ff..a41a023 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -17,9 +17,7 @@
 
 import android.app.ActivityManager;
 import android.os.BatteryStats;
-import android.os.Debug;
 import android.support.test.filters.SmallTest;
-import android.util.Log;
 
 import junit.framework.TestCase;
 
@@ -31,6 +29,9 @@
     private static final int UID = 10500;
     private static final int SENSOR_ID = -10000;
 
+    // TODO: fix the bug in StopwatchTimer to prevent this bug from manifesting here.
+    boolean revealCntBug = false;
+
     @SmallTest
     public void testSensorStartStop() throws Exception {
         final MockClocks clocks = new MockClocks();
@@ -38,7 +39,7 @@
         bi.mForceOnBattery = true;
         clocks.realtime = 100;
         clocks.uptime = 100;
-        bi.getOnBatteryTimeBase().setRunning(true, 100, 100);
+        bi.getOnBatteryTimeBase().setRunning(true, 100_000, 100_000);
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
         bi.noteStartSensorLocked(UID, SENSOR_ID);
@@ -56,58 +57,345 @@
 
         BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
                 .get(SENSOR_ID).getSensorTime();
-        BatteryStats.Counter sensorBgCounter = bi.getUidStats().get(UID).getSensorStats()
-                .get(SENSOR_ID).getSensorBgCount();
+        BatteryStats.Timer sensorBgTimer = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorBackgroundTime();
 
         assertEquals(2, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
-        assertEquals(300000,
-                sensorTimer.getTotalTimeLocked(clocks.realtime, BatteryStats.STATS_SINCE_CHARGED));
+        assertEquals(300_000, sensorTimer.getTotalTimeLocked(
+                clocks.realtime * 1000, BatteryStats.STATS_SINCE_CHARGED));
 
-        assertEquals(1, sensorBgCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        assertEquals(1, sensorBgTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        assertEquals(200_000, sensorBgTimer.getTotalTimeLocked(
+                clocks.realtime * 1000, BatteryStats.STATS_SINCE_CHARGED));
     }
 
     @SmallTest
-    public void testNestedSensorReset() throws Exception {
+    public void testCountingWhileOffBattery() throws Exception {
+        final MockClocks clocks = new MockClocks();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        long curr = 0; // realtime in us
+
+        // Plugged-in (battery=off, sensor=off)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+        bi.updateTimeBasesLocked(false, false, curr, curr);
+
+
+        // Start sensor (battery=off, sensor=on)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 200);
+        bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+        // Test situation
+        curr = 1000 * (clocks.realtime = clocks.uptime = 215);
+        BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorTime();
+        assertEquals(0,
+                sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        if(revealCntBug) {
+            assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        } else {
+            assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        }
+
+        // Stop sensor (battery=off, sensor=off)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 550);
+        bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+        // Test situation
+        curr = 1000 * (clocks.realtime = clocks.uptime = 678);
+        sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorTime();
+        assertEquals(0,
+                sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+    }
+
+    @SmallTest
+    public void testCountingWhileOnBattery() throws Exception {
+        final MockClocks clocks = new MockClocks();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        long curr = 0; // realtime in us
+
+        // Unplugged (battery=on, sensor=off)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+        bi.updateTimeBasesLocked(true, false, curr, curr);
+
+        // Start sensor (battery=on, sensor=on)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 200);
+        bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+        // Test situation
+        curr = 1000 * (clocks.realtime = clocks.uptime = 215);
+        BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorTime();
+        assertEquals((215-200)*1000,
+                sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+
+        // Stop sensor (battery=on, sensor=off)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 550);
+        bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+        // Test situation
+        curr = 1000 * (clocks.realtime = clocks.uptime = 678);
+        sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorTime();
+        assertEquals((550-200)*1000,
+                sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+    }
+
+    @SmallTest
+    public void testBatteryStatusOnToOff() throws Exception {
+        final MockClocks clocks = new MockClocks();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        long curr = 0; // realtime in us
+
+        // On battery (battery=on, sensor=off)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+        bi.updateTimeBasesLocked(true, false, curr, curr);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+        // Start sensor (battery=on, sensor=on)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+        bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+        // Off battery (battery=off, sensor=on)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+        bi.updateTimeBasesLocked(false, false, curr, curr);
+
+        // Stop sensor while off battery (battery=off, sensor=off)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+        bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+        // Start sensor while off battery (battery=off, sensor=on)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 519);
+        bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+        // Test while still running (but off battery)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+        BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorTime();
+        if(revealCntBug) {
+            assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        } else {
+            assertEquals(2, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        }
+        assertEquals((305-202)*1000,
+                sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+
+        // Now stop running (still off battery) (battery=off, sensor=off)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 693);
+        bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+        sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorTime();
+        assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        assertEquals((305-202)*1000,
+                sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+    }
+
+    @SmallTest
+    public void testBatteryStatusOffToOn() throws Exception {
+        final MockClocks clocks = new MockClocks();
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        long curr = 0; // realtime in us
+
+        // Plugged-in (battery=off, sensor=off)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+        bi.updateTimeBasesLocked(false, false, curr, curr);
+
+        // Start sensor (battery=off, sensor=on)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 200);
+        bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+        // Test situation
+        curr = 1000 * (clocks.realtime = clocks.uptime = 215);
+        BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorTime();
+        // Time was entirely off battery, so time=0.
+        assertEquals(0,
+                sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        // Acquired off battery, so count=0.
+        if(revealCntBug) {
+            assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        } else {
+            assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        }
+
+        // Unplug (battery=on, sensor=on)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+        bi.updateTimeBasesLocked(true, false, curr, curr);
+
+        //Test situation
+        curr = 1000 * (clocks.realtime = clocks.uptime = 410);
+        sensorTimer = bi.getUidStats().get(UID).getSensorStats().get(SENSOR_ID).getSensorTime();
+        // Part of the time it was on battery.
+        assertEquals((410-305)*1000,
+                sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        // Only ever acquired off battery, so count=0.
+        if(revealCntBug) {
+            assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        } else {
+            assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        }
+
+        // Stop sensor (battery=on, sensor=off)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 550);
+        bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+        // Test situation
+        curr = 1000 * (clocks.realtime = clocks.uptime = 678);
+        sensorTimer = bi.getUidStats().get(UID).getSensorStats().get(SENSOR_ID).getSensorTime();
+        // Part of the time it was on battery.
+        assertEquals((550-305)*1000,
+                sensorTimer.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        // Only ever acquired off battery, so count=0.
+        if(revealCntBug) {
+            assertEquals(0, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        } else {
+            assertEquals(1, sensorTimer.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        }
+    }
+
+    @SmallTest
+    public void testPooledBackgroundUsage() throws Exception {
+        final int UID_2 = 20000; // second uid for testing pool usage
         final MockClocks clocks = new MockClocks();
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
         bi.mForceOnBattery = true;
-        clocks.realtime = 100;
-        clocks.uptime = 100;
-        bi.getOnBatteryTimeBase().setRunning(true, 100, 100);
-        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_RECEIVER);
+        long curr = 0; // realtime in us
+        // Entire test is on-battery
+        curr = 1000 * (clocks.realtime = clocks.uptime = 1000);
+        bi.updateTimeBasesLocked(true, false, curr, curr);
 
-        clocks.realtime += 100;
-        clocks.uptime += 100;
+        // See below for a diagram of events.
 
+        // UID in foreground
+        curr = 1000 * (clocks.realtime = clocks.uptime = 2002);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+        // UID starts the sensor (foreground)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 3004);
         bi.noteStartSensorLocked(UID, SENSOR_ID);
 
-        clocks.realtime += 100;
-        clocks.uptime += 100;
+        // UID_2 in background
+        curr = 1000 * (clocks.realtime = clocks.uptime = 4008);
+        bi.noteUidProcessStateLocked(UID_2, ActivityManager.PROCESS_STATE_RECEIVER); // background
 
-        // The sensor is started and the background counter has been created.
-        final BatteryStats.Uid uid = bi.getUidStats().get(UID);
-        assertNotNull(uid);
+        // UID_2 starts the sensor (background)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 5016);
+        bi.noteStartSensorLocked(UID_2, SENSOR_ID);
 
-        BatteryStats.Uid.Sensor sensor = uid.getSensorStats().get(SENSOR_ID);
-        assertNotNull(sensor);
-        assertNotNull(sensor.getSensorTime());
-        assertNotNull(sensor.getSensorBgCount());
+        // UID enters background
+        curr = 1000 * (clocks.realtime = clocks.uptime = 6032);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
 
-        // Reset the stats. Since the sensor is still running, we should still see the sensor
-        // timer. Background counter should be gone though.
-        bi.getUidStatsLocked(UID).reset();
+        // UID enters background again (from a different background state)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 7004);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
 
-        sensor = uid.getSensorStats().get(SENSOR_ID);
-        assertNotNull(sensor);
-        assertNotNull(sensor.getSensorTime());
-        assertNull(sensor.getSensorBgCount());
+        // UID_2 stops the sensor (background), then starts it again, then stops again
+        curr = 1000 * (clocks.realtime = clocks.uptime = 8064);
+        bi.noteStopSensorLocked(UID_2, SENSOR_ID);
+        curr = 1000 * (clocks.realtime = clocks.uptime = 9128);
+        bi.noteStartSensorLocked(UID_2, SENSOR_ID);
+        curr = 1000 * (clocks.realtime = clocks.uptime = 10256);
+        bi.noteStopSensorLocked(UID_2, SENSOR_ID);
 
+        // UID re-enters foreground
+        curr = 1000 * (clocks.realtime = clocks.uptime = 11512);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+        // UID starts the sensor a second time (foreground)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 12000);
+        bi.noteStartSensorLocked(UID, SENSOR_ID);
+
+        // UID re-enters background
+        curr = 1000 * (clocks.realtime = clocks.uptime = 13002);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+        // UID stops the sensor completely (background)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 14004);
+        bi.noteStopSensorLocked(UID, SENSOR_ID);
+        curr = 1000 * (clocks.realtime = clocks.uptime = 14024);
         bi.noteStopSensorLocked(UID, SENSOR_ID);
 
-        // Now the sensor timer has stopped so this reset should also take out the sensor.
-        bi.getUidStatsLocked(UID).reset();
+        // UID starts the sensor anew (background)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 15010);
+        bi.noteStartSensorLocked(UID, SENSOR_ID);
 
-        sensor = uid.getSensorStats().get(SENSOR_ID);
-        assertNull(sensor);
+        // UID stops the sensor (background)
+        curr = 1000 * (clocks.realtime = clocks.uptime = 16020);
+        bi.noteStopSensorLocked(UID, SENSOR_ID);
+
+//      Summary
+//        UID
+//        foreground: 2002---6032,              11512---13002
+//        background:        6032---------------11512,  13002--------------------------
+//        sensor running: 3004-----------------------------14024, 15010-16020
+//
+//        UID2
+//        foreground:
+//        background:       4008-------------------------------------------------------
+//        sensor running:    5016--8064, 9128-10256
+
+        BatteryStats.Timer timer1 = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorTime();
+        BatteryStats.Timer bgTimer1 = bi.getUidStats().get(UID).getSensorStats()
+                .get(SENSOR_ID).getSensorBackgroundTime();
+
+        BatteryStats.Timer timer2 = bi.getUidStats().get(UID_2).getSensorStats()
+                .get(SENSOR_ID).getSensorTime();
+        BatteryStats.Timer bgTimer2 = bi.getUidStats().get(UID_2).getSensorStats()
+                .get(SENSOR_ID).getSensorBackgroundTime();
+
+        // Expected values
+        long expActualTime1 = (14024 - 3004) + (16020 - 15010);
+        long expBgTime1 = (11512 - 6032) + (14024 - 13002) + (16020 - 15010);
+
+        long expActualTime2 = (8064 - 5016) + (10256 - 9128);
+        long expBgTime2 = (8064 - 5016) + (10256 - 9128);
+
+        long expBlamedTime1 = (5016 - 3004) + (8064 - 5016)/2 + (9128 - 8064) + (10256 - 9128)/2
+                + (14024 - 10256) + (16020 - 15010);
+        long expBlamedTime2 = (8064 - 5016)/2 + (10256 - 9128)/2;
+
+        // Test: UID - blamed time
+        assertEquals(expBlamedTime1 * 1000,
+                timer1.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        // Test: UID - actual time
+        assertEquals(expActualTime1 * 1000,
+                timer1.getTotalDurationMsLocked(clocks.realtime) * 1000 );
+        // Test: UID - background time
+        // bg timer ignores pools, so both totalTime and totalDuration should give the same result
+        assertEquals(expBgTime1 * 1000,
+                bgTimer1.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        assertEquals(expBgTime1 * 1000,
+                bgTimer1.getTotalDurationMsLocked(clocks.realtime) * 1000 );
+        // Test: UID - count
+        assertEquals(2, timer1.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        // Test: UID - background count
+        if(revealCntBug) {
+            assertEquals(1, bgTimer1.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        } else {
+            assertEquals(2, bgTimer1.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        }
+
+        // Test: UID_2 - blamed time
+        assertEquals(expBlamedTime2 * 1000,
+                timer2.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        // Test: UID_2 - actual time
+        assertEquals(expActualTime2 * 1000,
+                timer2.getTotalDurationMsLocked(clocks.realtime) * 1000);
+        // Test: UID_2 - background time
+        // bg timer ignores pools, so both totalTime and totalDuration should give the same result
+        assertEquals(expBgTime2 * 1000,
+                bgTimer2.getTotalTimeLocked(curr, BatteryStats.STATS_SINCE_CHARGED));
+        assertEquals(expBgTime2 * 1000,
+                bgTimer2.getTotalDurationMsLocked(clocks.realtime) * 1000 );
+        // Test: UID_2 - count
+        assertEquals(2, timer2.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
+        // Test: UID_2 - background count
+        assertEquals(2, bgTimer2.getCountLocked(BatteryStats.STATS_SINCE_CHARGED));
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index c7cd0ee..1113268 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -12,6 +12,7 @@
         BatteryStatsTimerTest.class,
         BatteryStatsUidTest.class,
         BatteryStatsSensorTest.class,
+        BatteryStatsBackgroundStatsTest.class,
     })
 public class BatteryStatsTests {
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 1054106..65f898c 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -26,6 +26,7 @@
     MockBatteryStatsImpl(Clocks clocks) {
         super(clocks);
         this.clocks = mClocks;
+        mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
     }
 
     MockBatteryStatsImpl() {
@@ -39,5 +40,9 @@
     public boolean isOnBattery() {
         return mForceOnBattery ? true : super.isOnBattery();
     }
+
+    public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
+        return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
+    }
 }
 
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index acacd76..5603508 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -74,7 +74,6 @@
 const char* AssetManager::IDMAP_BIN = "/system/bin/idmap";
 const char* AssetManager::OVERLAY_DIR = "/vendor/overlay";
 const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme";
-const char* AssetManager::OVERLAY_THEME_DIR_PERSIST_PROPERTY = "persist.vendor.overlay.theme";
 const char* AssetManager::TARGET_PACKAGE_NAME = "android";
 const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk";
 const char* AssetManager::IDMAP_DIR = "/data/resource-cache";
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index becd307..0441b9d 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -66,11 +66,6 @@
      * OVERLAY_DIR.
      */
     static const char* OVERLAY_THEME_DIR_PROPERTY;
-    /**
-     * If OVERLAY_THEME_DIR_PERSIST_PROPERTY, use it to override
-     * OVERLAY_THEME_DIR_PROPERTY.
-     */
-    static const char* OVERLAY_THEME_DIR_PERSIST_PROPERTY;
     static const char* TARGET_PACKAGE_NAME;
     static const char* TARGET_APK_PATH;
     static const char* IDMAP_DIR;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
index 8f7d6ac..9e50490 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
@@ -25,6 +25,9 @@
 import android.content.res.AssetFileDescriptor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
@@ -805,6 +808,29 @@
         mFailedToCompleteWithNoError = true;
         String testResult;
 
+        final MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        final MediaExtractor extractor = new MediaExtractor();
+        boolean hasSupportedVideo = false;
+
+        try {
+            extractor.setDataSource(filePath);
+
+            for (int index = 0; index < extractor.getTrackCount(); ++index) {
+                MediaFormat format = extractor.getTrackFormat(index);
+                String mime = format.getString(MediaFormat.KEY_MIME);
+                if (!mime.startsWith("video/")) {
+                    continue;
+                }
+
+                if (list.findDecoderForFormat(format) != null) {
+                    hasSupportedVideo = true;
+                    break;
+                }
+            }
+        } finally {
+            extractor.release();
+        }
+
         initializeMessageLooper();
         synchronized (lock) {
             try {
@@ -820,7 +846,12 @@
             mMediaPlayer.setOnInfoListener(mInfoListener);
             Log.v(TAG, "playMediaSamples: sample file name " + filePath);
             mMediaPlayer.setDataSource(filePath);
-            mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
+            if (hasSupportedVideo) {
+                mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
+            } else {
+                Log.i(TAG, "Set no display due to no (supported) video track.");
+                mMediaPlayer.setDisplay(null);
+            }
             mMediaPlayer.prepare();
             duration = mMediaPlayer.getDuration();
             // start to play
diff --git a/packages/SettingsLib/res/xml/timezones.xml b/packages/SettingsLib/res/xml/timezones.xml
index 4426495..12d31cf 100644
--- a/packages/SettingsLib/res/xml/timezones.xml
+++ b/packages/SettingsLib/res/xml/timezones.xml
@@ -21,7 +21,7 @@
     <timezone id="America/St_Johns"></timezone>
     <timezone id="America/Recife"></timezone>
     <timezone id="America/Sao_Paulo"></timezone>
-    <timezone id="America/Buenos_Aires"></timezone>
+    <timezone id="America/Argentina/Buenos_Aires"></timezone>
     <timezone id="America/Godthab"></timezone>
     <timezone id="America/Montevideo"></timezone>
     <timezone id="Atlantic/South_Georgia"></timezone>
@@ -58,11 +58,11 @@
     <timezone id="Asia/Karachi"></timezone>
     <timezone id="Asia/Oral"></timezone>
     <timezone id="Asia/Yekaterinburg"></timezone>
-    <timezone id="Asia/Calcutta"></timezone>
+    <timezone id="Asia/Kolkata"></timezone>
     <timezone id="Asia/Colombo"></timezone>
-    <timezone id="Asia/Katmandu"></timezone>
+    <timezone id="Asia/Kathmandu"></timezone>
     <timezone id="Asia/Almaty"></timezone>
-    <timezone id="Asia/Rangoon"></timezone>
+    <timezone id="Asia/Yangon"></timezone>
     <timezone id="Asia/Krasnoyarsk"></timezone>
     <timezone id="Asia/Bangkok"></timezone>
     <timezone id="Asia/Jakarta"></timezone>
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index 9bb3c36..350b648 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -169,6 +169,19 @@
         return context.getString(R.string.config_defaultAccessibilityService);
     }
 
+    /**
+     * Check if the accessibility shortcut is enabled for a user
+     *
+     * @param context A valid context
+     * @param userId The user of interest
+     * @return {@code true} if the shortcut is enabled for the user. {@code false} otherwise.
+     *         Note that the shortcut may be enabled, but no action associated with it.
+     */
+    public static boolean isShortcutEnabled(Context context, int userId) {
+        return Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, userId) == 1;
+    }
+
     private static Set<ComponentName> getInstalledServices(Context context) {
         final Set<ComponentName> installedServices = new HashSet<>();
         installedServices.clear();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 55c886e..0280f26 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -95,7 +95,7 @@
 
     private WifiTrackerNetworkCallback mNetworkCallback;
 
-    private boolean mSavedNetworksExist;
+    private int mNumSavedNetworks;
     private boolean mRegistered;
 
     /** Updated using main handler. Clone of this collection is returned
@@ -363,11 +363,11 @@
     }
 
     /**
-     * @return true when there are saved networks on the device, regardless
-     * of whether the WifiTracker is tracking saved networks.
+     * Returns the number of saved networks on the device, regardless of whether the WifiTracker
+     * is tracking saved networks.
      */
-    public boolean doSavedNetworksExist() {
-        return mSavedNetworksExist;
+    public int getNumSavedNetworks() {
+        return mNumSavedNetworks;
     }
 
     public boolean isConnected() {
@@ -461,11 +461,12 @@
 
         final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
         if (configs != null) {
-            mSavedNetworksExist = configs.size() != 0;
+            mNumSavedNetworks = 0;
             for (WifiConfiguration config : configs) {
                 if (config.selfAdded && config.numAssociation == 0) {
                     continue;
                 }
+                mNumSavedNetworks++;
                 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
                 if (mLastInfo != null && mLastNetworkInfo != null) {
                     accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index e100884..46726f2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -395,6 +395,26 @@
     }
 
     @Test
+    public void testGetNumSavedNetworks() throws InterruptedException {
+        WifiConfiguration validConfig = new WifiConfiguration();
+        validConfig.SSID = SSID_1;
+        validConfig.BSSID = BSSID_1;
+
+        WifiConfiguration selfAddedNoAssociation = new WifiConfiguration();
+        selfAddedNoAssociation.selfAdded = true;
+        selfAddedNoAssociation.numAssociation = 0;
+        selfAddedNoAssociation.SSID = SSID_2;
+        selfAddedNoAssociation.BSSID = BSSID_2;
+
+        when(mockWifiManager.getConfiguredNetworks())
+                .thenReturn(Arrays.asList(validConfig, selfAddedNoAssociation));
+
+        WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
+
+        assertEquals(1, tracker.getNumSavedNetworks());
+    }
+
+    @Test
     public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException {
         WifiTracker tracker =  createTrackerWithScanResultsAndAccessPoint1Connected();
 
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index f5c3d40..deb494f 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -25,5 +25,5 @@
         android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
         android:textColor="?android:attr/textColorPrimary"
         android:gravity="center_vertical|start"
-        android:paddingStart="@dimen/battery_level_padding_start"
+        android:paddingEnd="@dimen/battery_level_padding_start"
         />
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 14f2c4a..8c1062b 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -181,6 +181,7 @@
                 if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
                 updatePercentText();
                 addView(mBatteryPercentView,
+                        0,
                         new ViewGroup.LayoutParams(
                                 LayoutParams.WRAP_CONTENT,
                                 LayoutParams.MATCH_PARENT));
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index d058e78..79190cb 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -24,6 +24,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.NightDisplayController;
+import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.Preconditions;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.assist.AssistManager;
@@ -257,6 +258,8 @@
         mProviders.put(VolumeDialogController.class, () ->
                 new VolumeDialogControllerImpl(mContext));
 
+        mProviders.put(MetricsLogger.class, () -> new MetricsLogger());
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8580085..fbb075a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -95,6 +95,7 @@
      */
     private final Class<?>[] SERVICES_PER_USER = new Class[] {
             Dependency.class,
+            NotificationChannels.class,
             Recents.class
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 1709718..9efe224 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.qs;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
@@ -197,7 +199,7 @@
             mDetailContent.removeAllViews();
             mDetailContent.addView(detailView);
             mDetailViews.put(viewCacheIndex, detailView);
-            MetricsLogger.visible(mContext, adapter.getMetricsCategory());
+            Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
             announceForAccessibility(mContext.getString(
                     R.string.accessibility_quick_settings_detail,
                     adapter.getTitle()));
@@ -206,7 +208,7 @@
             setVisibility(View.VISIBLE);
         } else {
             if (mDetailAdapter != null) {
-                MetricsLogger.hidden(mContext, mDetailAdapter.getMetricsCategory());
+                Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
             }
             mClosingDetail = true;
             mDetailAdapter = null;
@@ -238,8 +240,12 @@
     protected void setupDetailFooter(DetailAdapter adapter) {
         final Intent settingsIntent = adapter.getSettingsIntent();
         mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
-        mDetailSettingsButton.setOnClickListener(v -> Dependency.get(ActivityStarter.class)
-                .postStartActivityDismissingKeyguard(settingsIntent, 0));
+        mDetailSettingsButton.setOnClickListener(v -> {
+            Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
+                    mDetailAdapter.getMetricsCategory());
+            Dependency.get(ActivityStarter.class)
+                    .postStartActivityDismissingKeyguard(settingsIntent, 0);
+        });
     }
 
     protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 2202b58..a84138d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
+
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -358,6 +360,8 @@
                 startSettingsActivity();
             }
         } else if (v == mDateTimeGroup) {
+            Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
+                    mNextAlarm != null);
             if (mNextAlarm != null) {
                 PendingIntent showIntent = mNextAlarm.getShowIntent();
                 mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 29d547c..8596b57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -32,6 +32,8 @@
     TileServices getTileServices();
     void removeTile(String tileSpec);
 
+    int indexOf(String tileSpec);
+
     interface Callback {
         void onTilesChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8298cbb..2e6116d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -59,6 +59,7 @@
     protected final View mBrightnessView;
     private final H mHandler = new H();
     private final View mPageIndicator;
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     private int mPanelPaddingBottom;
     private int mBrightnessPaddingTop;
@@ -259,7 +260,7 @@
         if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
             ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
         }
-        MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
+        mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
         if (!mExpanded) {
             closeDetail();
         } else {
@@ -475,7 +476,7 @@
         int newVis = visible ? VISIBLE : INVISIBLE;
         setVisibility(newVis);
         if (mGridContentVisible != visible) {
-            MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
+            mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
         }
         mGridContentVisible = visible;
     }
@@ -483,7 +484,7 @@
     private void logTiles() {
         for (int i = 0; i < mRecords.size(); i++) {
             TileRecord tileRecord = mRecords.get(i);
-            MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
+            mMetricsLogger.visible(tileRecord.tile.getMetricsCategory());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0ca115e..9330541 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -157,6 +157,10 @@
         return mServices;
     }
 
+    public int indexOf(String spec) {
+        return mTileSpecs.indexOf(spec);
+    }
+
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (!TILES_SETTING.equals(key)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 6f35017..b5c1bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -22,6 +22,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
@@ -155,6 +156,11 @@
         return mComponent;
     }
 
+    @Override
+    protected LogMaker populate(LogMaker logMaker) {
+        return super.populate(logMaker).setComponentName(mComponent);
+    }
+
     public Tile getQsTile() {
         return mTile;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 948954c2..1aa51b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -14,12 +14,19 @@
 
 package com.android.systemui.qs.tileimpl;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -29,7 +36,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
@@ -58,6 +64,7 @@
     protected final H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
     protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
     private final ArraySet<Object> mListeners = new ArraySet<>();
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     protected TState mState = newTileState();
@@ -76,7 +83,7 @@
     /**
      * Declare the category of this tile.
      *
-     * Categories are defined in {@link com.android.internal.logging.MetricsProto.MetricsEvent}
+     * Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}
      * by editing frameworks/base/proto/src/metrics_constants.proto.
      */
     abstract public int getMetricsCategory();
@@ -152,17 +159,28 @@
     }
 
     public void click() {
+        mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)));
         mHandler.sendEmptyMessage(H.CLICK);
     }
 
     public void secondaryClick() {
+        mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)));
         mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
     }
 
     public void longClick() {
+        mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)));
         mHandler.sendEmptyMessage(H.LONG_CLICK);
     }
 
+    protected LogMaker populate(LogMaker logMaker) {
+        if (mState instanceof BooleanState) {
+            logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0);
+        }
+        return logMaker.setSubtype(getMetricsCategory())
+                .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));
+    }
+
     public void showDetail(boolean show) {
         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
     }
@@ -224,7 +242,6 @@
     }
 
     protected void handleLongClick() {
-        MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
         Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
                 getLongClickIntent(), 0);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index ed6e6ef..4e4de15 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -83,7 +83,6 @@
     protected void handleClick() {
         // Secondary clicks are header clicks, just toggle.
         final boolean isEnabled = (Boolean)mState.value;
-        MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
         mController.setBluetoothEnabled(!isEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index a1d3d26..22b6a63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -108,13 +108,11 @@
     protected void handleClick() {
         if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
             mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
-                MetricsLogger.action(mContext, getMetricsCategory());
                 showDetail(true);
                 mHost.openPanels();
             });
             return;
         }
-        MetricsLogger.action(mContext, getMetricsCategory());
         showDetail(true);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 4351b2c..04be7de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -98,7 +98,6 @@
 
     @Override
     protected void handleSecondaryClick() {
-        MetricsLogger.action(mContext, getMetricsCategory());
         if (mDataController.isMobileDataSupported()) {
             showDetail(true);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index e33b680..5b374b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -84,7 +84,6 @@
 
     @Override
     protected void handleClick() {
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         mSetting.setValue(mState.value ? 0 : 1);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7a25140..b796451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -87,7 +87,6 @@
 
     private void toggleDataSaver() {
         mState.value = !mDataSaverController.isDataSaverEnabled();
-        MetricsLogger.action(mContext, getMetricsCategory(), mState.value);
         mDataSaverController.setDataSaverEnabled(mState.value);
         refreshState(mState.value);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f35de68..3c2e897 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -146,7 +146,6 @@
                     Toast.LENGTH_LONG).show();
             return;
         }
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         showDetail(true);
         int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
         mController.setZen(zen, null, TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 7b0fd73..6d2aa90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -87,7 +87,6 @@
         if (ActivityManager.isUserAMonkey()) {
             return;
         }
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         boolean newState = !mState.value;
         refreshState(newState);
         mFlashlightController.setFlashlight(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 6662937..5c3f65c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -106,7 +106,6 @@
         if (!isEnabled && mAirplaneMode.getValue() != 0) {
             return;
         }
-        MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
         mController.setHotspotEnabled(!isEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index c953363..00cfbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -92,7 +92,6 @@
 
     @Override
     protected void handleClick() {
-        MetricsLogger.action(mContext, getMetricsCategory(), mIntentPackage);
         sendIntent("click", mOnClick, mOnClickUri);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index b5c02cb..b11b15a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -81,13 +81,11 @@
             Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
                 final boolean wasEnabled = mState.value;
                 mHost.openPanels();
-                MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
                 mController.setLocationEnabled(!wasEnabled);
             });
             return;
         }
         final boolean wasEnabled = mState.value;
-        MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
         mController.setLocationEnabled(!wasEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index 3299339..d147b69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -85,7 +85,6 @@
     @Override
     protected void handleClick() {
         if (mAdapter == null) return;
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         if (!mAdapter.isEnabled()) {
             mAdapter.enable();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 8b47216..8aa1e43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -54,7 +54,6 @@
     @Override
     protected void handleClick() {
         final boolean activated = !mState.value;
-        MetricsLogger.action(mContext, getMetricsCategory(), activated);
         mController.setActivated(activated);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 130304f..fb937bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -79,7 +79,6 @@
     @Override
     protected void handleClick() {
         if (mController == null) return;
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         final boolean newState = !mState.value;
         mController.setRotationLocked(!newState);
         refreshState(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index fde2e04..79b4c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -114,7 +114,6 @@
     protected void handleClick() {
         // Secondary clicks are header clicks, just toggle.
         mState.copyTo(mStateBeforeClick);
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         mController.setWifiEnabled(!mState.value);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 5086091..6c89241 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -68,7 +68,6 @@
 
     @Override
     public void handleClick() {
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         mProfileController.setWorkModeEnabled(!mState.value);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index b5f56c3..4d99a46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -22,6 +22,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 
@@ -33,7 +34,7 @@
     private ArrayMap<Integer, Integer> mLegacyMap;
     private LogMaker mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
             .setType(MetricsEvent.TYPE_ACTION);
-    private MetricsLogger mMetricsLogger = new MetricsLogger();
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     public LockscreenGestureLogger() {
         mLegacyMap = new ArrayMap<>(EventLogConstants.METRICS_GESTURE_TYPE_MAP.length);
@@ -58,9 +59,4 @@
         }
         return value;
     }
-
-    @VisibleForTesting
-    void setMetricsLogger(MetricsLogger metricsLogger) {
-        mMetricsLogger = metricsLogger;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 5fb642f..1f03024 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -557,9 +557,9 @@
     }
 
     private boolean onAccessibilityLongClick(View v) {
-        // TODO(b/34720082): Target service selection via long click
-        android.widget.Toast.makeText(getContext(), "Service selection coming soon...",
-                android.widget.Toast.LENGTH_LONG).show();
+        Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        v.getContext().startActivity(intent);
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index b82b113..bfe55bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -484,7 +484,7 @@
 
     private ScreenPinningRequest mScreenPinningRequest;
 
-    MetricsLogger mMetricsLogger = new MetricsLogger();
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     // ensure quick settings is disabled until the current user makes it through the setup wizard
     private boolean mUserSetup = false;
@@ -748,12 +748,6 @@
     private NavigationBarFragment mNavigationBar;
     private View mNavigationBarView;
 
-    @VisibleForTesting
-    void setMetricsLogger(MetricsLogger metricsLogger) {
-        mMetricsLogger = metricsLogger;
-        mLockscreenGestureLogger.setMetricsLogger(metricsLogger);
-    }
-
     @Override
     public void start() {
         mNetworkController = Dependency.get(NetworkController.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
new file mode 100644
index 0000000..c67cccc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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.qs;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.ViewUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.DetailAdapter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSDetailTest extends SysuiTestCase {
+
+    private MetricsLogger mMetricsLogger;
+    private QSDetail mQsDetail;
+    private QSPanel mQsPanel;
+    private QuickStatusBarHeader mQuickHeader;
+    private ActivityStarter mActivityStarter;
+    private DetailAdapter mMockDetailAdapter;
+    private TestableLooper mTestableLooper;
+
+    @Before
+    public void setup() throws Exception {
+        mTestableLooper = TestableLooper.get(this);
+        mTestableLooper.runWithLooper(() -> {
+            mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+            mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
+            mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
+            mQsPanel = mock(QSPanel.class);
+            mQuickHeader = mock(QuickStatusBarHeader.class);
+            mQsDetail.setQsPanel(mQsPanel, mQuickHeader);
+
+            mMockDetailAdapter = mock(DetailAdapter.class);
+            when(mMockDetailAdapter.createDetailView(any(), any(), any()))
+                    .thenReturn(mock(View.class));
+        });
+    }
+
+    @Test
+    public void testShowDetail_Metrics() {
+        ViewUtils.attachView(mQsDetail);
+        mTestableLooper.processAllMessages();
+
+        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+        verify(mMetricsLogger).visible(eq(mMockDetailAdapter.getMetricsCategory()));
+        mQsDetail.handleShowingDetail(null, 0, 0, false);
+        verify(mMetricsLogger).hidden(eq(mMockDetailAdapter.getMetricsCategory()));
+
+        ViewUtils.detachView(mQsDetail);
+        mTestableLooper.processAllMessages();
+    }
+
+    @Test
+    public void testMoreSettingsButton() {
+        ViewUtils.attachView(mQsDetail);
+        mTestableLooper.processAllMessages();
+
+        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+        mQsDetail.findViewById(android.R.id.button2).performClick();
+
+        int metricsCategory = mMockDetailAdapter.getMetricsCategory();
+        verify(mMetricsLogger).action(eq(ACTION_QS_MORE_SETTINGS), eq(metricsCategory));
+
+        verify(mActivityStarter).postStartActivityDismissingKeyguard(any(), anyInt());
+
+        ViewUtils.detachView(mQsDetail);
+        mTestableLooper.processAllMessages();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index deb31da..d77ed3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -19,6 +19,7 @@
 
 import android.os.Looper;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.CarrierText;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -44,12 +45,15 @@
 @RunWithLooper(setAsMainLooper = true)
 public class QSFragmentTest extends SysuiBaseFragmentTest {
 
+    private MetricsLogger mMockMetricsLogger;
+
     public QSFragmentTest() {
         super(QSFragment.class);
     }
 
     @Before
     public void addLeakCheckDependencies() {
+        mMockMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
                 new LayoutInflaterBuilder(mContext)
                         .replace("com.android.systemui.statusbar.policy.SplitClockView",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
new file mode 100644
index 0000000..4979684
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 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.qs;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.customize.QSCustomizer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSPanelTest extends SysuiTestCase {
+
+    private MetricsLogger mMetricsLogger;
+    private QSPanel mQsPanel;
+    private QSTileHost mHost;
+    private QSCustomizer mCustomizer;
+
+    @Before
+    public void setup() throws Exception {
+        TestableLooper.get(this).runWithLooper(() -> {
+            mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+            mQsPanel = new QSPanel(mContext, null);
+            mHost = mock(QSTileHost.class);
+            when(mHost.getTiles()).thenReturn(Collections.emptyList());
+            mCustomizer = mock(QSCustomizer.class);
+            mQsPanel.setHost(mHost, mCustomizer);
+        });
+    }
+
+    @Test
+    public void testSetExpanded_Metrics() {
+        mQsPanel.setExpanded(true);
+        verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(true));
+        mQsPanel.setExpanded(false);
+        verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(false));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
new file mode 100644
index 0000000..9ed9d28
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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.qs.tileimpl;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.metrics.LogMaker;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QSTileHost;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSTileImplTest extends SysuiTestCase {
+
+    public static final int POSITION = 14;
+    private TestableLooper mTestableLooper;
+    private TileImpl mTile;
+    private QSTileHost mHost;
+    private MetricsLogger mMetricsLogger;
+
+    @Before
+    public void setup() throws Exception {
+        String spec = "spec";
+        mTestableLooper = TestableLooper.get(this);
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+        mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+        mHost = mock(QSTileHost.class);
+        when(mHost.indexOf(spec)).thenReturn(POSITION);
+        mTestableLooper.runWithLooper(() -> {
+            mTile = new TileImpl(mHost);
+            mTile.setTileSpec(spec);
+        });
+    }
+
+    @Test
+    public void testClick_Metrics() {
+        mTile.click();
+        verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_CLICK)));
+    }
+
+    @Test
+    public void testSecondaryClick_Metrics() {
+        mTile.secondaryClick();
+        verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
+    }
+
+    @Test
+    public void testLongClick_Metrics() {
+        mTile.longClick();
+        verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS)));
+    }
+
+    @Test
+    public void testPopulate() {
+        LogMaker maker = mock(LogMaker.class);
+        when(maker.setSubtype(anyInt())).thenReturn(maker);
+        mTile.getState().value = true;
+        mTile.populate(maker);
+        verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1));
+        verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION));
+    }
+
+    private class TileLogMatcher implements ArgumentMatcher<LogMaker> {
+
+        private final int mCategory;
+        public String mInvalid;
+
+        public TileLogMatcher(int category) {
+            mCategory = category;
+        }
+
+        @Override
+        public boolean matches(LogMaker arg) {
+            if (arg.getCategory() != mCategory) {
+                mInvalid = "Expected category " + mCategory + " but was " + arg.getCategory();
+                return false;
+            }
+            if (arg.getType() != TYPE_ACTION) {
+                mInvalid = "Expected type " + TYPE_ACTION + " but was " + arg.getType();
+                return false;
+            }
+            if (arg.getSubtype() != mTile.getMetricsCategory()) {
+                mInvalid = "Expected subtype " + mTile.getMetricsCategory() + " but was "
+                        + arg.getSubtype();
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return mInvalid;
+        }
+    }
+
+    private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
+        protected TileImpl(QSHost host) {
+            super(host);
+        }
+
+        @Override
+        public BooleanState newTileState() {
+            return new BooleanState();
+        }
+
+        @Override
+        protected void handleClick() {
+
+        }
+
+        @Override
+        protected void handleUpdateState(BooleanState state, Object arg) {
+
+        }
+
+        @Override
+        public int getMetricsCategory() {
+            return 42;
+        }
+
+        @Override
+        public Intent getLongClickIntent() {
+            return null;
+        }
+
+        @Override
+        protected void setListening(boolean listening) {
+
+        }
+
+        @Override
+        public CharSequence getTileLabel() {
+            return null;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index f48af75..bf6b394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -62,9 +62,9 @@
         mKeyguardIndicationController = mock(KeyguardIndicationController.class);
         mStackScroller = mock(NotificationStackScrollLayout.class);
         mMetricsLogger = new FakeMetricsLogger();
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
         mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
                 mKeyguardIndicationController, mStackScroller);
-        mStatusBar.setMetricsLogger(mMetricsLogger);
 
         doAnswer(invocation -> {
             OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 57d2581..da441f5 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -45,16 +45,16 @@
     // The view or control was updated.
     TYPE_UPDATE = 6;
 
-    // Type for APP_TRANSITION event: The transition started a new activity for which it's process
-    // wasn't running.
+    // Type for APP_TRANSITION event: The transition started a new
+    // activity for which it's process wasn't running.
     TYPE_TRANSITION_COLD_LAUNCH = 7;
 
-    // Type for APP_TRANSITION event: The transition started a new activity for which it's process
-    // was already running.
+    // Type for APP_TRANSITION event: The transition started a new
+    // activity for which it's process was already running.
     TYPE_TRANSITION_WARM_LAUNCH = 8;
 
-    // Type for APP_TRANSITION event: The transition brought an already existing activity to the
-    // front.
+    // Type for APP_TRANSITION event: The transition brought an
+    // already existing activity to the front.
     TYPE_TRANSITION_HOT_LAUNCH = 9;
 
     // The action was successful
@@ -64,6 +64,80 @@
     TYPE_FAILURE = 11;
   }
 
+  // Types of alerts, as bit field values
+  enum Alert {
+    // Vibrate the device.
+    ALERT_BUZZ = 1;
+
+    // Make sound through the speaker.
+    ALERT_BEEP = 2;
+
+    // Flash a notificaiton light.
+    ALERT_BLINK = 4;
+  }
+
+  // Reasons that a notification might be dismissed.
+  enum DismissReason {
+    // from android.service.notification.NotificationListenerService
+
+    // Notification was canceled by the status bar reporting a notification click
+    REASON_CLICK = 1;
+
+    // Notification was canceled by the status bar reporting a user dismissal.
+    REASON_CANCEL = 2;
+
+    // Notification was canceled by the status bar reporting a user dismiss all.
+    REASON_CANCEL_ALL = 3;
+
+    // Notification was canceled by the status bar reporting an inflation error.
+    REASON_ERROR = 4;
+
+    // Notification was canceled by the package manager modifying the package.
+    REASON_PACKAGE_CHANGED = 5;
+
+    // Notification was canceled by the owning user context being stopped.
+    REASON_USER_STOPPED = 6;
+
+    // Notification was canceled by the user banning the package.
+    REASON_PACKAGE_BANNED = 7;
+
+    // Notification was canceled by the app canceling this specific notification.
+    REASON_APP_CANCEL = 8;
+
+    //Notification was canceled by the app cancelling all its notifications.
+    REASON_APP_CANCEL_ALL = 9;
+
+    // Notification was canceled by a listener reporting a user dismissal.
+    REASON_LISTENER_CANCEL = 10;
+
+    //Notification was canceled by a listener reporting a user dismiss all.
+    REASON_LISTENER_CANCEL_ALL = 11;
+
+    // Notification was canceled because it was a member of a canceled group.
+    REASON_GROUP_SUMMARY_CANCELED = 12;
+
+    // Notification was canceled because it was an invisible member of a group.
+    REASON_GROUP_OPTIMIZATION = 13;
+
+    // Notification was canceled by the device administrator suspending the package.
+    REASON_PACKAGE_SUSPENDED = 14;
+
+    // Notification was canceled by the owning managed profile being turned off.
+    REASON_PROFILE_TURNED_OFF = 15;
+
+    // Autobundled summary notification was canceled because its group was unbundled.
+    REASON_UNAUTOBUNDLED = 16;
+
+    // Notification was canceled by the user banning the channel.
+    REASON_CHANNEL_BANNED = 17;
+
+    // Notification was snoozed.
+    REASON_SNOOZED = 18;
+
+    // Notification was canceled due to timeout.
+    REASON_TIMEOUT = 19;
+  }
+
   // Known visual elements: views or controls.
   enum View {
     // Unknown view
@@ -97,7 +171,9 @@
     // OS: 6.0
     ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE = 6;
 
-    // OPEN: Settings > Accessibility > Magnification gestures
+    // OPEN: Settings > Accessibility > Magnification gestures (Renamed in O)
+    // OPEN: Settings > Accessibility > Magnification > Magnify with triple-tap
+    // OPEN: Settings > Accessibility > Magnification > Magnify with button
     // CATEGORY: SETTINGS
     // OS: 6.0
     ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION = 7;
@@ -1881,7 +1957,9 @@
     // OS: N
     SUW_ACCESSIBILITY = 367;
 
-    // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification gesture
+    // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification gestures (Renamed in O)
+    // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification -> Magnify with triple-tap
+    // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification -> Magnify with button
     // ACTION: New magnification gesture configuration is chosen
     //  SUBTYPE: 0 is off, 1 is on
     // CATEGORY: SETTINGS
@@ -3578,7 +3656,7 @@
     ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE = 870;
 
     // The name of the activity being launched in an app transition event.
-    APP_TRANSITION_ACTIVITY_NAME = 871;
+    FIELD_CLASS_NAME = 871;
 
     // ACTION: Settings > App detail > Uninstall
     ACTION_SETTINGS_UNINSTALL_APP = 872;
@@ -3783,6 +3861,37 @@
     // OPEN: Settings -> Display -> When in VR Mode
     VR_DISPLAY_PREFERENCE = 921;
 
+    // OPEN: Settings > Accessibility > Magnification
+    // CATEGORY: SETTINGS
+    // OS: O
+    ACCESSIBILITY_SCREEN_MAGNIFICATION_SETTINGS = 922;
+
+    // ACTION: Logs pressing the "Clear app" button in the app info settings page for an instant
+    // app.
+    // VALUE: The package name of the app
+    ACTION_SETTINGS_CLEAR_INSTANT_APP = 923;
+
+    // OPEN: Settings -> System -> Reset options
+    RESET_DASHBOARD = 924;
+
+    // ACTION: QS -> Tile clicked
+    ACTION_QS_CLICK = 925;
+
+    // ACTION: QS -> Secondary click
+    ACTION_QS_SECONDARY_CLICK = 926;
+
+    // FIELD: Position info in QS clicks
+    FIELD_QS_POSITION = 927;
+
+    // FIELD: The value of a QS tile when clicked (if applicable)
+    FIELD_QS_VALUE = 928;
+
+    // ACTION: QS -> Detail panel -> more settings
+    ACTION_QS_MORE_SETTINGS = 929;
+
+    // ACTION: QS -> Click date
+    ACTION_QS_DATE = 930;
+
     // ---- End O Constants, all O constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 397938a..05c6592 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -70,6 +70,7 @@
 import android.os.UserManager;
 import android.os.UserManagerInternal;
 import android.provider.Settings;
+import android.provider.SettingsStringUtil;
 import android.provider.SettingsStringUtil.ComponentNameSet;
 import android.provider.SettingsStringUtil.SettingStringHelper;
 import android.text.TextUtils;
@@ -100,7 +101,6 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.IntPair;
 import com.android.server.LocalServices;
@@ -1154,17 +1154,55 @@
 
     private void notifyAccessibilityButtonClickedLocked() {
         final UserState state = getCurrentUserStateLocked();
-        if (state.mIsNavBarMagnificationEnabled) {
-            mMainHandler.obtainMessage(
-                    MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
-        } else {
-            for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-                final Service service = state.mBoundServices.get(i);
-                // TODO(b/34720082): Only notify a single user-defined service
-                if (service.mRequestAccessibilityButton) {
-                    service.notifyAccessibilityButtonClickedLocked();
+
+        int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
+        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+            final Service service = state.mBoundServices.get(i);
+            if (service.mRequestAccessibilityButton) {
+                potentialTargets++;
+            }
+        }
+
+        if (potentialTargets == 0) {
+            return;
+        }
+        if (potentialTargets == 1) {
+            if (state.mIsNavBarMagnificationEnabled) {
+                mMainHandler.obtainMessage(
+                        MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
+                return;
+            } else {
+                for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+                    final Service service = state.mBoundServices.get(i);
+                    if (service.mRequestAccessibilityButton) {
+                        service.notifyAccessibilityButtonClickedLocked();
+                        return;
+                    }
                 }
             }
+        } else {
+            if (state.mServiceAssignedToAccessibilityButton == null
+                    && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+                mMainHandler.obtainMessage(
+                        MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget();
+            } else if (state.mIsNavBarMagnificationEnabled
+                    && state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+                mMainHandler.obtainMessage(
+                        MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
+                return;
+            } else {
+                for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+                    final Service service = state.mBoundServices.get(i);
+                    if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
+                            state.mServiceAssignedToAccessibilityButton))) {
+                        service.notifyAccessibilityButtonClickedLocked();
+                        return;
+                    }
+                }
+            }
+            // The user may have turned off the assigned service or feature
+            mMainHandler.obtainMessage(
+                    MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget();
         }
     }
 
@@ -1534,6 +1572,12 @@
         }
     }
 
+    private void showAccessibilityButtonTargetSelection() {
+        Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+    }
+
     private void scheduleNotifyClientsOfServicesStateChange(UserState userState) {
         mMainHandler.obtainMessage(MainHandler.MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS,
                 userState.mUserId).sendToTarget();
@@ -1681,6 +1725,7 @@
         scheduleUpdateInputFilter(userState);
         scheduleUpdateClientsIfNeededLocked(userState);
         updateRelevantEventsLocked(userState);
+        updateAccessibilityButtonTargets(userState);
     }
 
     private void updateAccessibilityFocusBehaviorLocked(UserState userState) {
@@ -1794,6 +1839,7 @@
         somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
         somethingChanged |= readAutoclickEnabledSettingLocked(userState);
         somethingChanged |= readAccessibilityShortcutSettingLocked(userState);
+        somethingChanged |= readAccessibilityButtonSettingsLocked(userState);
         return somethingChanged;
     }
 
@@ -1920,13 +1966,45 @@
         }
         ComponentName componentNameToEnable =
             ComponentName.unflattenFromString(componentNameToEnableString);
-        if (componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
+        if ((componentNameToEnable != null)
+                && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
             return false;
         }
         userState.mServiceToEnableWithShortcut = componentNameToEnable;
         return true;
     }
 
+    private boolean readAccessibilityButtonSettingsLocked(UserState userState) {
+        String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId);
+        if (TextUtils.isEmpty(componentId)) {
+            if ((userState.mServiceAssignedToAccessibilityButton == null)
+                    && !userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+                return false;
+            }
+            userState.mServiceAssignedToAccessibilityButton = null;
+            userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+            return true;
+        }
+
+        if (componentId.equals(MagnificationController.class.getName())) {
+            if (userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+                return false;
+            }
+            userState.mServiceAssignedToAccessibilityButton = null;
+            userState.mIsNavBarMagnificationAssignedToAccessibilityButton = true;
+            return true;
+        }
+
+        ComponentName componentName = ComponentName.unflattenFromString(componentId);
+        if (componentName.equals(userState.mServiceAssignedToAccessibilityButton)) {
+            return false;
+        }
+        userState.mServiceAssignedToAccessibilityButton = componentName;
+        userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+        return true;
+    }
+
     /**
      * Check if the service that will be enabled by the shortcut is installed. If it isn't,
      * clear the value and the associated setting so a sideloaded service can't spoof the
@@ -1948,7 +2026,9 @@
         if (!shortcutServiceIsInstalled) {
             userState.mServiceToEnableWithShortcut = null;
             Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", userState.mUserId);
+                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null, userState.mUserId);
+            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0, userState.mUserId);
         }
     }
 
@@ -2135,6 +2215,22 @@
         }
     }
 
+    private void updateAccessibilityButtonTargets(UserState userState) {
+        final List<Service> services;
+        synchronized (mLock) {
+            services = userState.mBoundServices;
+            int numServices = services.size();
+            for (int i = 0; i < numServices; i++) {
+                final Service service = services.get(i);
+                if (service.mRequestAccessibilityButton) {
+                    boolean available = service.mComponentName.equals(
+                            userState.mServiceAssignedToAccessibilityButton);
+                    service.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+                }
+            }
+        }
+    }
+
     @GuardedBy("mLock")
     private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
         IBinder windowToken = mGlobalWindowTokens.get(windowId);
@@ -2209,7 +2305,7 @@
      * Disables accessibility service specified by {@param componentName} for the {@param userId}.
      */
     private void disableAccessibilityServiceLocked(ComponentName componentName, int userId) {
-        final SettingStringHelper setting =
+        final SettingsStringUtil.SettingStringHelper setting =
                 new SettingStringHelper(
                         mContext.getContentResolver(),
                         Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
@@ -2339,6 +2435,7 @@
         public static final int MSG_UPDATE_FINGERPRINT = 11;
         public static final int MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS = 12;
         public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13;
+        public static final int MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER = 14;
 
         public MainHandler(Looper looper) {
             super(looper);
@@ -2432,6 +2529,10 @@
                             mInputFilter.notifyAccessibilityButtonClicked();
                         }
                     }
+                } break;
+
+                case MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER: {
+                    showAccessibilityButtonTargetSelection();
                 }
             }
         }
@@ -4810,6 +4911,8 @@
         public int mSoftKeyboardShowMode = 0;
 
         public boolean mIsAccessibilityButtonAvailable;
+        public boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
+        public ComponentName mServiceAssignedToAccessibilityButton;
 
         public boolean mIsTouchExplorationEnabled;
         public boolean mIsTextHighContrastEnabled;
@@ -4885,6 +4988,8 @@
             mIsEnhancedWebAccessibilityEnabled = false;
             mIsDisplayMagnificationEnabled = false;
             mIsNavBarMagnificationEnabled = false;
+            mServiceAssignedToAccessibilityButton = null;
+            mIsNavBarMagnificationAssignedToAccessibilityButton = false;
             mIsAutoclickEnabled = false;
             mSoftKeyboardShowMode = 0;
 
@@ -4950,6 +5055,9 @@
         private final Uri mAccessibilityShortcutServiceIdUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
 
+        private final Uri mAccessibilityButtonComponentIdUri = Settings.Secure.getUriFor(
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+
         public AccessibilityContentObserver(Handler handler) {
             super(handler);
         }
@@ -4982,6 +5090,8 @@
                     mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(
                     mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL);
+            contentResolver.registerContentObserver(
+                    mAccessibilityButtonComponentIdUri, false, this, UserHandle.USER_ALL);
         }
 
         @Override
@@ -5039,6 +5149,10 @@
                     if (readAccessibilityShortcutSettingLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
+                } else if (mAccessibilityButtonComponentIdUri.equals(uri)) {
+                    if (readAccessibilityButtonSettingsLocked(userState)) {
+                        onUserStateChangedLocked(userState);
+                    }
                 }
             }
         }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index a77e533..72d37ad 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -365,7 +365,6 @@
             activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
             appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
             autofillId = Preconditions.checkNotNull(autofillId, "autoFillId");
-            bounds = Preconditions.checkNotNull(bounds, "bounds");
             packageName = Preconditions.checkNotNull(packageName, "packageName");
 
             Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId");
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 6fb9f7c..4d78350 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -71,8 +71,7 @@
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
-import android.view.autofill.AutofillManager.AutofillCallback;
-
+import android.view.autofill.IAutofillWindowPresenter;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
@@ -298,17 +297,17 @@
     }
 
     void startSessionLocked(@NonNull IBinder activityToken, @Nullable IBinder windowToken,
-            @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect bounds,
-            @Nullable AutofillValue value, boolean hasCallback, int flags,
-            @NonNull String packageName) {
+            @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
+            @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
+            int flags, @NonNull String packageName) {
         if (!isEnabled()) {
             return;
         }
 
         final String historyItem = "s=" + mInfo.getServiceInfo().packageName
                 + " u=" + mUserId + " a=" + activityToken
-
-                + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback + " f=" + flags;
+                + " i=" + autofillId + " b=" + virtualBounds + " hc=" + hasCallback
+                + " f=" + flags;
         mRequestsHistory.log(historyItem);
 
         // TODO(b/33197203): Handle partitioning
@@ -320,7 +319,7 @@
 
         final Session newSession = createSessionByTokenLocked(activityToken,
                 windowToken, appCallbackToken, hasCallback, flags, packageName);
-        newSession.updateLocked(autofillId, bounds, value, FLAG_START_SESSION);
+        newSession.updateLocked(autofillId, virtualBounds, value, FLAG_START_SESSION);
     }
 
     void finishSessionLocked(IBinder activityToken) {
@@ -388,7 +387,7 @@
         return newSession;
     }
 
-    void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect bounds,
+    void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect virtualBounds,
             AutofillValue value, int flags) {
         final Session session = mSessions.get(activityToken);
         if (session == null) {
@@ -398,7 +397,7 @@
             return;
         }
 
-        session.updateLocked(autofillId, bounds, value, flags);
+        session.updateLocked(autofillId, virtualBounds, value, flags);
     }
 
     private void handleSessionSave(IBinder activityToken) {
@@ -508,8 +507,8 @@
             /**
              * Called when the fill UI is ready to be shown for this view.
              */
-            void onFillReady(ViewState viewState, FillResponse fillResponse, Rect bounds,
-                    AutofillId focusedId, @Nullable AutofillValue value);
+            void onFillReady(FillResponse fillResponse, AutofillId focusedId,
+                    @Nullable AutofillValue value);
         }
 
         final AutofillId mId;
@@ -522,7 +521,9 @@
         Intent mAuthIntent;
 
         private AutofillValue mAutofillValue;
-        private Rect mBounds;
+
+        // Bounds if a virtual view, null otherwise
+        private Rect mVirtualBounds;
 
         private boolean mValueUpdated;
 
@@ -555,12 +556,12 @@
         // TODO(b/33197203): need to refactor / rename / document this method to make it clear that
         // it can change  the value and update the UI; similarly, should replace code that
         // directly sets mAutoFilLValue to use encapsulation.
-        void update(@Nullable AutofillValue autofillValue, @Nullable Rect bounds) {
+        void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) {
             if (autofillValue != null) {
                 mAutofillValue = autofillValue;
             }
-            if (bounds != null) {
-                mBounds = bounds;
+            if (virtualBounds != null) {
+                mVirtualBounds = virtualBounds;
             }
 
             maybeCallOnFillReady();
@@ -568,19 +569,19 @@
 
         /**
          * Calls {@link
-         * Listener#onFillReady(ViewState, FillResponse, Rect, AutofillId, AutofillValue)} if the
+         * Listener#onFillReady(FillResponse, AutofillId, AutofillValue)} if the
          * fill UI is ready to be displayed (i.e. when response and bounds are set).
          */
         void maybeCallOnFillReady() {
             if (mResponse != null && (mResponse.getAuthentication() != null
-                    || mResponse.getDatasets() != null) && mBounds != null) {
-                mListener.onFillReady(this, mResponse, mBounds, mId, mAutofillValue);
+                    || mResponse.getDatasets() != null)) {
+                mListener.onFillReady(mResponse, mId, mAutofillValue);
             }
         }
 
         @Override
         public String toString() {
-            return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mBounds
+            return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds
                     + ", updated = " + mValueUpdated + "]";
         }
 
@@ -588,7 +589,7 @@
             pw.print(prefix); pw.print("id:" ); pw.println(mId);
             pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue);
             pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
-            pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds);
+            pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
             pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
         }
     }
@@ -817,8 +818,24 @@
 
         // AutoFillUiCallback
         @Override
-        public void onEvent(AutofillId id, int event) {
-            mHandlerCaller.getHandler().post(() -> notifyChangeToClient(id, event));
+        public void requestShowFillUi(AutofillId id, int width, int height,
+                IAutofillWindowPresenter presenter) {
+            try {
+                mClient.requestShowFillUi(mWindowToken, id, width, height,
+                        mCurrentViewState.mVirtualBounds, presenter);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error requesting to show fill UI", e);
+            }
+        }
+
+        // AutoFillUiCallback
+        @Override
+        public void requestHideFillUi(AutofillId id) {
+            try {
+                mClient.requestHideFillUi(mWindowToken, id);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error requesting to hide fill UI", e);
+            }
         }
 
         public void setAuthenticationResultLocked(Bundle data) {
@@ -835,10 +852,11 @@
                     processResponseLocked(mCurrentResponse);
                 } else if (result instanceof Dataset) {
                     Dataset dataset = (Dataset) result;
-                    mCurrentResponse.getDatasets().remove(mAutoFilledDataset);
-                    mCurrentResponse.getDatasets().add(dataset);
-                    mAutoFilledDataset = dataset;
-                    processResponseLocked(mCurrentResponse);
+                    final int index = mCurrentResponse.getDatasets().indexOf(mAutoFilledDataset);
+                    if (index >= 0) {
+                        mCurrentResponse.getDatasets().set(index, dataset);
+                        autoFill(dataset);
+                    }
                 }
             }
         }
@@ -1009,7 +1027,7 @@
             mRemoteFillService.onSaveRequest(mStructure, extras);
         }
 
-        void updateLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) {
+        void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
             if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
                 // TODO(b/33197203): ignoring because we don't support partitions yet
                 Slog.d(TAG, "updateLocked(): ignoring " + flags + " after app was autofilled");
@@ -1025,7 +1043,7 @@
             if ((flags & FLAG_START_SESSION) != 0) {
                 // View is triggering autofill.
                 mCurrentViewState = viewState;
-                viewState.update(value, bounds);
+                viewState.update(value, virtualBounds);
                 return;
             }
 
@@ -1065,7 +1083,7 @@
                 }
 
                 // If the ViewState is ready to be displayed, onReady() will be called.
-                viewState.update(value, bounds);
+                viewState.update(value, virtualBounds);
 
                 // TODO(b/33197203): Remove when there is a response per activity.
                 if (mCurrentResponse != null) {
@@ -1087,23 +1105,14 @@
         }
 
         @Override
-        public void onFillReady(ViewState viewState, FillResponse response, Rect bounds,
-                AutofillId filledId, @Nullable AutofillValue value) {
+        public void onFillReady(FillResponse response, AutofillId filledId,
+                @Nullable AutofillValue value) {
             String filterText = null;
             if (value != null && value.isText()) {
                 filterText = value.getTextValue().toString();
             }
 
-            getUiForShowing().showFillUi(filledId, response, bounds, filterText, mPackageName);
-        }
-
-        private void notifyChangeToClient(AutofillId id, int event) {
-            if (!mHasCallback) return;
-            try {
-                mClient.onAutofillEvent(mWindowToken, id, event);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Error notifying client on change: id=" + id + ", event=" + event, e);
-            }
+            getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
         }
 
         private void notifyUnavailableToClient() {
@@ -1112,7 +1121,13 @@
                 Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null");
                 return;
             }
-            notifyChangeToClient(mCurrentViewState.mId, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
+            if (!mHasCallback) return;
+            try {
+                mClient.notifyNoFillUi(mWindowToken, mCurrentViewState.mId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken
+                        + " id=" + mCurrentViewState.mId, e);
+            }
         }
 
         private void processResponseLocked(FillResponse response) {
@@ -1212,7 +1227,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
                     }
-                    mClient.autofill(dataset.getFieldIds(), dataset.getFieldValues());
+                    mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues());
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Error autofilling activity: " + e);
                 }
@@ -1221,7 +1236,7 @@
 
         private AutoFillUI getUiForShowing() {
             synchronized (mLock) {
-                mUi.setCallback(this, mWindowToken);
+                mUi.setCallback(this);
                 return mUi;
             }
         }
@@ -1261,8 +1276,7 @@
 
         private void destroyLocked() {
             mRemoteFillService.destroy();
-            mUi.setCallback(null, null);
-
+            mUi.setCallback(null);
             mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_FINISHED,
                     mPackageName);
         }
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 299b456..003c8f1 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -43,7 +43,6 @@
 
 import com.android.internal.os.HandlerCaller;
 import com.android.server.FgThread;
-import com.android.server.autofill.AutofillManagerServiceImpl.Session;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -89,10 +88,13 @@
     private PendingRequest mPendingRequest;
 
     public interface FillServiceCallbacks {
-        void onFillRequestSuccess(@Nullable FillResponse response, @NonNull String servicePackageName);
-        void onFillRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName);
+        void onFillRequestSuccess(@Nullable FillResponse response,
+                @NonNull String servicePackageName);
+        void onFillRequestFailure(@Nullable CharSequence message,
+                @NonNull String servicePackageName);
         void onSaveRequestSuccess(@NonNull String servicePackageName);
-        void onSaveRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName);
+        void onSaveRequestFailure(@Nullable CharSequence message,
+                @NonNull String servicePackageName);
         void onServiceDied(RemoteFillService service);
         void onDisableSelf();
     }
@@ -243,14 +245,10 @@
         }
         mBinding = false;
         if (isBound()) {
-            // TODO(b/33197203): synchronize access instead?
-            // Need to double check if it's null, since it could be set on onServiceDisconnected()
-            if (mAutoFillService != null) {
-                try {
-                    mAutoFillService.onInit(null);
-                } catch (Exception e) {
-                    Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e);
-                }
+            try {
+                mAutoFillService.onInit(null);
+            } catch (Exception e) {
+                Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e);
             }
             if (mAutoFillService != null) {
                 mAutoFillService.asBinder().unlinkToDeath(this, 0);
@@ -323,19 +321,13 @@
                 handleBinderDied();
                 return;
             }
-
             try {
-                // TODO(b/33197203): synchronize access instead?
-                // Need to double check if it's null, since it could be set on
-                // onServiceDisconnected()
-                if (mAutoFillService != null) {
-                    mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() {
-                        @Override
-                        public void disableSelf() {
-                            mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget();
-                        }
-                    });
-                }
+                mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() {
+                    @Override
+                    public void disableSelf() {
+                        mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget();
+                    }
+                });
             } catch (RemoteException e) {
                 Slog.w(LOG_TAG, "Exception calling onConnected(): " + e);
             }
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 375a726..2555cee 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -15,19 +15,14 @@
  */
 package com.android.server.autofill.ui;
 
-import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
-import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
-
 import static com.android.server.autofill.ui.Helper.DEBUG;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.IntentSender;
-import android.graphics.Rect;
 import android.metrics.LogMaker;
 import android.os.Handler;
-import android.os.IBinder;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillResponse;
 import android.service.autofill.SaveInfo;
@@ -35,6 +30,7 @@
 import android.text.format.DateUtils;
 import android.util.Slog;
 import android.view.autofill.AutofillId;
+import android.view.autofill.IAutofillWindowPresenter;
 import android.widget.Toast;
 
 import com.android.internal.logging.MetricsLogger;
@@ -62,7 +58,6 @@
     private @Nullable SaveUi mSaveUi;
 
     private @Nullable AutoFillUiCallback mCallback;
-    private @Nullable IBinder mWindowToken;
 
     private int mSaveTimeoutMs = (int) (5 * DateUtils.SECOND_IN_MILLIS);
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -72,20 +67,20 @@
         void fill(@NonNull Dataset dataset);
         void save();
         void cancelSave();
-        void onEvent(AutofillId id, int event);
+        void requestShowFillUi(AutofillId id, int width, int height,
+                IAutofillWindowPresenter presenter);
+        void requestHideFillUi(AutofillId id);
     }
 
     public AutoFillUI(@NonNull Context context) {
         mContext = context;
     }
 
-    public void setCallback(@Nullable AutoFillUiCallback callback,
-            @Nullable IBinder windowToken) {
+    public void setCallback(@Nullable AutoFillUiCallback callback) {
         mHandler.post(() -> {
-            if (mCallback != callback || mWindowToken != windowToken) {
+            if (mCallback != callback) {
                 hideAllUiThread();
                 mCallback = callback;
-                mWindowToken = windowToken;
             }
         });
     }
@@ -109,12 +104,7 @@
      * Hides the fill UI.
      */
     public void hideFillUi(AutofillId id) {
-        mHandler.post(() -> {
-            hideFillUiUiThread();
-            if (mCallback != null) {
-                mCallback.onEvent(id, EVENT_INPUT_HIDDEN);
-            }
-        });
+        mHandler.post(this::hideFillUiUiThread);
     }
 
     /**
@@ -135,36 +125,17 @@
     }
 
     /**
-     * Updates the position of the fill UI.
-     *
-     * @param anchoredBounds The bounds of the anchor view.
-     */
-    public void updateFillUi(@NonNull Rect anchoredBounds) {
-        mHandler.post(() -> {
-            if (!hasCallback()) {
-                return;
-            }
-            hideSaveUiUiThread();
-            if (mFillUi != null) {
-                mFillUi.update(anchoredBounds);
-            }
-        });
-    }
-
-    /**
      * Shows the fill UI, removing the previous fill UI if the has changed.
      *
      * @param focusedId the currently focused field
      * @param response the current fill response
-     * @param anchorBounds bounds of the focused view
      * @param filterText text of the view to be filled
      * @param packageName package name of the activity that is filled
      */
     public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
-            @NonNull Rect anchorBounds, @Nullable String filterText, @NonNull String packageName) {
+            @Nullable String filterText, @NonNull String packageName) {
         if (DEBUG) {
-            Slog.d(TAG, "showFillUi(): id=" + focusedId + ", bounds=" + anchorBounds + " filter="
-                    + filterText);
+            Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + filterText);
         }
         final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
                 .setPackageName(packageName)
@@ -179,7 +150,7 @@
             }
             hideAllUiThread();
             mFillUi = new FillUi(mContext, response, focusedId,
-                    mWindowToken, anchorBounds, filterText, new FillUi.Callback() {
+                    filterText, new FillUi.Callback() {
                 @Override
                 public void onResponsePicked(FillResponse response) {
                     log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL);
@@ -211,8 +182,22 @@
                     }
                     mMetricsLogger.write(log);
                 }
+
+                @Override
+                public void requestShowFillUi(int width, int height,
+                        IAutofillWindowPresenter windowPresenter) {
+                    if (mCallback != null) {
+                        mCallback.requestShowFillUi(focusedId, width, height, windowPresenter);
+                    }
+                }
+
+                @Override
+                public void requestHideFillUi() {
+                    if (mCallback != null) {
+                        mCallback.requestHideFillUi(focusedId);
+                    }
+                }
             });
-            mCallback.onEvent(focusedId, EVENT_INPUT_SHOWN);
         });
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 98a02f2..d38fb96 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -18,14 +18,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.IBinder;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillResponse;
 import android.util.Slog;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -34,11 +30,13 @@
 import android.view.WindowManager;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutofillWindowPresenter;
 import android.widget.ArrayAdapter;
 import android.widget.ListView;
 import android.widget.RemoteViews;
 
 import com.android.internal.R;
+import com.android.server.UiThread;
 import libcore.util.Objects;
 
 import java.io.PrintWriter;
@@ -54,9 +52,13 @@
         void onDatasetPicked(@NonNull Dataset dataset);
         void onCanceled();
         void onDestroy();
+        void requestShowFillUi(int width, int height,
+                IAutofillWindowPresenter windowPresenter);
+        void requestHideFillUi();
     }
 
-    private final Rect mAnchorBounds = new Rect();
+    private final @NonNull AutofillWindowPresenter mWindowPresenter =
+            new AutofillWindowPresenter();
 
     private final @NonNull AnchoredWindow mWindow;
 
@@ -75,10 +77,8 @@
     private boolean mDestroyed;
 
     FillUi(@NonNull Context context, @NonNull FillResponse response,
-            @NonNull AutofillId focusedViewId, @NonNull IBinder windowToken,
-            @NonNull Rect anchorBounds, @Nullable String filterText,
+            @NonNull AutofillId focusedViewId, @NonNull @Nullable String filterText,
             @NonNull Callback callback) {
-        mAnchorBounds.set(anchorBounds);
         mCallback = callback;
 
         mAccessibilityTitle = context.getString(R.string.autofill_picker_accessibility_title);
@@ -104,8 +104,8 @@
             mContentWidth = content.getMeasuredWidth();
             mContentHeight = content.getMeasuredHeight();
 
-            mWindow = new AnchoredWindow(windowToken, content);
-            mWindow.show(mContentWidth, mContentHeight, mAnchorBounds);
+            mWindow = new AnchoredWindow(content);
+            mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter);
         } else {
             final int datasetCount = response.getDatasets().size();
             final ArrayList<ViewItem> items = new ArrayList<>(datasetCount);
@@ -153,15 +153,7 @@
             }
 
             applyNewFilterText();
-            mWindow = new AnchoredWindow(windowToken, mListView);
-        }
-    }
-
-    public void update(@NonNull Rect anchorBounds) {
-        throwIfDestroyed();
-        if (!mAnchorBounds.equals(anchorBounds)) {
-            mAnchorBounds.set(anchorBounds);
-            mWindow.show(mContentWidth, mContentHeight, anchorBounds);
+            mWindow = new AnchoredWindow(mListView);
         }
     }
 
@@ -171,10 +163,10 @@
                 return;
             }
             if (count <= 0) {
-                mWindow.hide();
+                mCallback.requestHideFillUi();
             } else {
                 if (updateContentSize()) {
-                    mWindow.show(mContentWidth, mContentHeight, mAnchorBounds);
+                    mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter);
                 }
                 if (mAdapter.getCount() > VISIBLE_OPTIONS_MAX_COUNT) {
                     mListView.setVerticalScrollBarEnabled(true);
@@ -209,7 +201,7 @@
     public void destroy() {
         throwIfDestroyed();
         mCallback.onDestroy();
-        mWindow.hide();
+        mCallback.requestHideFillUi();
         mDestroyed = true;
     }
 
@@ -285,33 +277,61 @@
         }
     }
 
+    private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub {
+        @Override
+        public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
+                boolean fitsSystemWindows, int layoutDirection) {
+            UiThread.getHandler().post(() -> mWindow.show(p));
+        }
+
+        @Override
+        public void hide(Rect transitionEpicenter) {
+            UiThread.getHandler().post(mWindow::hide);
+        }
+    }
+
     final class AnchoredWindow implements View.OnTouchListener {
-        private final Point mTempPoint = new Point();
-
         private final WindowManager mWm;
-
-        private final IBinder mActivityToken;
         private final View mContentView;
+        private boolean mShowing;
 
         /**
          * Constructor.
          *
-         * @param activityToken token to pass to window manager
          * @param contentView content of the window
          */
-        AnchoredWindow(IBinder activityToken, View contentView) {
+        AnchoredWindow(View contentView) {
             mWm = contentView.getContext().getSystemService(WindowManager.class);
-            mActivityToken = activityToken;
             mContentView = contentView;
         }
 
         /**
+         * Shows the window.
+         */
+        public void show(WindowManager.LayoutParams params) {
+            try {
+                if (!mShowing) {
+                    params.accessibilityTitle = mAccessibilityTitle;
+                    mWm.addView(mContentView, params);
+                    mContentView.setOnTouchListener(this);
+                    mShowing = true;
+                } else {
+                    mWm.updateViewLayout(mContentView, params);
+                }
+            } catch (WindowManager.BadTokenException e) {
+                Slog.i(TAG, "Filed with with token " + params.token + " gone.");
+                mCallback.onDestroy();
+            }
+        }
+
+        /**
          * Hides the window.
          */
         void hide() {
-            if (mContentView.isAttachedToWindow()) {
+            if (mShowing) {
                 mContentView.setOnTouchListener(null);
                 mWm.removeView(mContentView);
+                mShowing = false;
             }
         }
 
@@ -324,82 +344,9 @@
             }
             return false;
         }
-
-        public void show(int desiredWidth, int desiredHeight, Rect anchorBounds) {
-            try {
-                // TODO: temporary workaround to avoud system_server crashes.
-                unsafelyShow(desiredWidth, desiredHeight, anchorBounds);
-            } catch (RuntimeException e) {
-                Slog.w(TAG, "Error showing Anchored window: w=" + desiredWidth + ", h="
-                        + desiredHeight + ", b=" + anchorBounds, e);
-            }
-        }
-
-        private void unsafelyShow(int desiredWidth, int desiredHeight, Rect anchorBounds) {
-            final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-
-            params.setTitle("FillUi");
-            params.token = mActivityToken;
-            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-            params.accessibilityTitle = mAccessibilityTitle;
-
-            mWm.getDefaultDisplay().getRealSize(mTempPoint);
-            final int screenWidth = mTempPoint.x;
-            final int screenHeight = mTempPoint.y;
-
-            // Try to place the window at the start of the anchor view if
-            // there is space to fit the content, otherwise fit as much of
-            // the window as possible moving it to the left using all available
-            // screen width.
-            params.x = Math.min(anchorBounds.left, Math.max(screenWidth - desiredWidth, 0));
-            params.width = Math.min(screenWidth, desiredWidth);
-
-            // Try to fit below using all available space with top-start gravity
-            // and if that fails try to fit above using all available space with
-            // bottom-start gravity.
-            final int verticalSpaceBelow = screenHeight - anchorBounds.bottom;
-            if (desiredHeight <= verticalSpaceBelow) {
-                // Fits below bounds.
-                params.height = desiredHeight;
-                params.gravity = Gravity.TOP | Gravity.START;
-                params.y = anchorBounds.bottom;
-            } else {
-                final int verticalSpaceAbove = anchorBounds.top;
-                if (desiredHeight <= verticalSpaceAbove) {
-                    // Fits above bounds.
-                    params.height = desiredHeight;
-                    params.gravity = Gravity.BOTTOM | Gravity.START;
-                    params.y = anchorBounds.top + desiredHeight;
-                } else {
-                    // Pick above/below based on which has the most space.
-                    if (verticalSpaceBelow >= verticalSpaceAbove) {
-                        params.height = verticalSpaceBelow;
-                        params.gravity = Gravity.TOP | Gravity.START;
-                        params.y = anchorBounds.bottom;
-                    } else {
-                        params.height = verticalSpaceAbove;
-                        params.gravity = Gravity.BOTTOM | Gravity.START;
-                        params.y = anchorBounds.top + desiredHeight;
-                    }
-                }
-            }
-
-            if (!mContentView.isAttachedToWindow()) {
-                mWm.addView(mContentView, params);
-                mContentView.setOnTouchListener(this);
-            } else {
-                mWm.updateViewLayout(mContentView, params);
-            }
-        }
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("mAnchorBounds: "); pw.println(mAnchorBounds);
         pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
         pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
         pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 30d06db..037804e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -222,12 +222,27 @@
     // 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
     // 3 : introduced "_meta" metadata file; no other format change per se
     // 4 : added support for new device-encrypted storage locations
-    static final int BACKUP_FILE_VERSION = 4;
+    // 5 : added support for key-value packages
+    static final int BACKUP_FILE_VERSION = 5;
     static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
     static final int BACKUP_PW_FILE_VERSION = 2;
     static final String BACKUP_METADATA_FILENAME = "_meta";
     static final int BACKUP_METADATA_VERSION = 1;
     static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
+
+    static final int TAR_HEADER_LONG_RADIX = 8;
+    static final int TAR_HEADER_OFFSET_FILESIZE = 124;
+    static final int TAR_HEADER_LENGTH_FILESIZE = 12;
+    static final int TAR_HEADER_OFFSET_MODTIME = 136;
+    static final int TAR_HEADER_LENGTH_MODTIME = 12;
+    static final int TAR_HEADER_OFFSET_MODE = 100;
+    static final int TAR_HEADER_LENGTH_MODE = 8;
+    static final int TAR_HEADER_OFFSET_PATH_PREFIX = 345;
+    static final int TAR_HEADER_LENGTH_PATH_PREFIX = 155;
+    static final int TAR_HEADER_OFFSET_PATH = 0;
+    static final int TAR_HEADER_LENGTH_PATH = 100;
+    static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
+
     static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
 
     static final String SETTINGS_PACKAGE = "com.android.providers.settings";
@@ -553,19 +568,20 @@
         }
     }
 
-    class FullParams {
+    // Parameters used by adbBackup() and adbRestore()
+    class AdbParams {
         public ParcelFileDescriptor fd;
         public final AtomicBoolean latch;
         public IFullBackupRestoreObserver observer;
         public String curPassword;     // filled in by the confirmation step
         public String encryptPassword;
 
-        FullParams() {
+        AdbParams() {
             latch = new AtomicBoolean(false);
         }
     }
 
-    class FullBackupParams extends FullParams {
+    class AdbBackupParams extends AdbParams {
         public boolean includeApks;
         public boolean includeObbs;
         public boolean includeShared;
@@ -573,11 +589,12 @@
         public boolean allApps;
         public boolean includeSystem;
         public boolean doCompress;
+        public boolean includeKeyValue;
         public String[] packages;
 
-        FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
+        AdbBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
                 boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem,
-                boolean compress, String[] pkgList) {
+                boolean compress, boolean doKeyValue, String[] pkgList) {
             fd = output;
             includeApks = saveApks;
             includeObbs = saveObbs;
@@ -586,12 +603,13 @@
             allApps = doAllApps;
             includeSystem = doSystem;
             doCompress = compress;
+            includeKeyValue = doKeyValue;
             packages = pkgList;
         }
     }
 
-    class FullRestoreParams extends FullParams {
-        FullRestoreParams(ParcelFileDescriptor input) {
+    class AdbRestoreParams extends AdbParams {
+        AdbRestoreParams(ParcelFileDescriptor input) {
             fd = input;
         }
     }
@@ -627,10 +645,10 @@
     static final int OP_TIMEOUT = -1;
 
     // Waiting for backup agent to respond during backup operation.
-    private static final int OP_TYPE_BACKUP_WAIT = 0;
+    static final int OP_TYPE_BACKUP_WAIT = 0;
 
     // Waiting for backup agent to respond during restore operation.
-    private static final int OP_TYPE_RESTORE_WAIT = 1;
+    static final int OP_TYPE_RESTORE_WAIT = 1;
 
     // An entire backup operation spanning multiple packages.
     private static final int OP_TYPE_BACKUP = 2;
@@ -672,7 +690,7 @@
     final Object mCurrentOpLock = new Object();
     final Random mTokenGenerator = new Random();
 
-    final SparseArray<FullParams> mFullConfirmations = new SparseArray<FullParams>();
+    final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<AdbParams>();
 
     // Where we keep our journal files and other bookkeeping
     File mBaseStateDir;
@@ -791,15 +809,9 @@
     }
 
     /* adb backup: is this app only capable of doing key/value?  We say otherwise if
-     * the app has a backup agent and does not say fullBackupOnly, *unless* it
-     * is a package that we know _a priori_ explicitly supports both key/value and
-     * full-data backup.
+     * the app has a backup agent and does not say fullBackupOnly,
      */
     private static boolean appIsKeyValueOnly(PackageInfo pkg) {
-        if ("com.android.providers.settings".equals(pkg.packageName)) {
-            return false;
-        }
-
         return !appGetsFullBackup(pkg);
     }
 
@@ -912,13 +924,12 @@
             {
                 // TODO: refactor full backup to be a looper-based state machine
                 // similar to normal backup/restore.
-                FullBackupParams params = (FullBackupParams)msg.obj;
+                AdbBackupParams params = (AdbBackupParams)msg.obj;
                 PerformAdbBackupTask task = new PerformAdbBackupTask(params.fd,
                         params.observer, params.includeApks, params.includeObbs,
-                        params.includeShared, params.doWidgets,
-                        params.curPassword, params.encryptPassword,
-                        params.allApps, params.includeSystem, params.doCompress,
-                        params.packages, params.latch);
+                        params.includeShared, params.doWidgets, params.curPassword,
+                        params.encryptPassword, params.allApps, params.includeSystem,
+                        params.doCompress, params.includeKeyValue, params.packages, params.latch);
                 (new Thread(task, "adb-backup")).start();
                 break;
             }
@@ -963,7 +974,7 @@
             {
                 // TODO: refactor full restore to be a looper-based state machine
                 // similar to normal backup/restore.
-                FullRestoreParams params = (FullRestoreParams)msg.obj;
+                AdbRestoreParams params = (AdbRestoreParams)msg.obj;
                 PerformAdbRestoreTask task = new PerformAdbRestoreTask(params.fd,
                         params.curPassword, params.encryptPassword,
                         params.observer, params.latch);
@@ -1071,16 +1082,16 @@
 
             case MSG_FULL_CONFIRMATION_TIMEOUT:
             {
-                synchronized (mFullConfirmations) {
-                    FullParams params = mFullConfirmations.get(msg.arg1);
+                synchronized (mAdbBackupRestoreConfirmations) {
+                    AdbParams params = mAdbBackupRestoreConfirmations.get(msg.arg1);
                     if (params != null) {
                         Slog.i(TAG, "Full backup/restore timed out waiting for user confirmation");
 
                         // Release the waiter; timeout == completion
-                        signalFullBackupRestoreCompletion(params);
+                        signalAdbBackupRestoreCompletion(params);
 
                         // Remove the token from the set
-                        mFullConfirmations.delete(msg.arg1);
+                        mAdbBackupRestoreConfirmations.delete(msg.arg1);
 
                         // Report a timeout to the observer, if any
                         if (params.observer != null) {
@@ -3719,7 +3730,7 @@
 
     }
 
-    private void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
+    static void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
             throws IOException {
         // We do not take close() responsibility for the pipe FD
         FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
@@ -3822,7 +3833,7 @@
                     if (mWriteManifest) {
                         final boolean writeWidgetData = mWidgetData != null;
                         if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
-                        writeAppManifest(mPackage, mManifestFile, mSendApk, writeWidgetData);
+                        writeAppManifest(mPackage, mPackageManager, mManifestFile, mSendApk, writeWidgetData);
                         FullBackup.backupToTar(mPackage.packageName, null, null,
                                 mFilesDir.getAbsolutePath(),
                                 mManifestFile.getAbsolutePath(),
@@ -4006,52 +4017,6 @@
             }
         }
 
-        private void writeAppManifest(PackageInfo pkg, File manifestFile,
-                boolean withApk, boolean withWidgets) throws IOException {
-            // Manifest format. All data are strings ending in LF:
-            //     BACKUP_MANIFEST_VERSION, currently 1
-            //
-            // Version 1:
-            //     package name
-            //     package's versionCode
-            //     platform versionCode
-            //     getInstallerPackageName() for this package (maybe empty)
-            //     boolean: "1" if archive includes .apk; any other string means not
-            //     number of signatures == N
-            // N*:    signature byte array in ascii format per Signature.toCharsString()
-            StringBuilder builder = new StringBuilder(4096);
-            StringBuilderPrinter printer = new StringBuilderPrinter(builder);
-
-            printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
-            printer.println(pkg.packageName);
-            printer.println(Integer.toString(pkg.versionCode));
-            printer.println(Integer.toString(Build.VERSION.SDK_INT));
-
-            String installerName = mPackageManager.getInstallerPackageName(pkg.packageName);
-            printer.println((installerName != null) ? installerName : "");
-
-            printer.println(withApk ? "1" : "0");
-            if (pkg.signatures == null) {
-                printer.println("0");
-            } else {
-                printer.println(Integer.toString(pkg.signatures.length));
-                for (Signature sig : pkg.signatures) {
-                    printer.println(sig.toCharsString());
-                }
-            }
-
-            FileOutputStream outstream = new FileOutputStream(manifestFile);
-            outstream.write(builder.toString().getBytes());
-            outstream.close();
-
-            // We want the manifest block in the archive stream to be idempotent:
-            // each time we generate a backup stream for the app, we want the manifest
-            // block to be identical.  The underlying tar mechanism sees it as a file,
-            // though, and will propagate its mtime, causing the tar header to vary.
-            // Avoid this problem by pinning the mtime to zero.
-            manifestFile.setLastModified(0);
-        }
-
         // Widget metadata format. All header entries are strings ending in LF:
         //
         // Version 1 header:
@@ -4100,6 +4065,52 @@
         }
     }
 
+    static void writeAppManifest(PackageInfo pkg, PackageManager packageManager, File manifestFile,
+            boolean withApk, boolean withWidgets) throws IOException {
+        // Manifest format. All data are strings ending in LF:
+        //     BACKUP_MANIFEST_VERSION, currently 1
+        //
+        // Version 1:
+        //     package name
+        //     package's versionCode
+        //     platform versionCode
+        //     getInstallerPackageName() for this package (maybe empty)
+        //     boolean: "1" if archive includes .apk; any other string means not
+        //     number of signatures == N
+        // N*:    signature byte array in ascii format per Signature.toCharsString()
+        StringBuilder builder = new StringBuilder(4096);
+        StringBuilderPrinter printer = new StringBuilderPrinter(builder);
+
+        printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
+        printer.println(pkg.packageName);
+        printer.println(Integer.toString(pkg.versionCode));
+        printer.println(Integer.toString(Build.VERSION.SDK_INT));
+
+        String installerName = packageManager.getInstallerPackageName(pkg.packageName);
+        printer.println((installerName != null) ? installerName : "");
+
+        printer.println(withApk ? "1" : "0");
+        if (pkg.signatures == null) {
+            printer.println("0");
+        } else {
+            printer.println(Integer.toString(pkg.signatures.length));
+            for (Signature sig : pkg.signatures) {
+                printer.println(sig.toCharsString());
+            }
+        }
+
+        FileOutputStream outstream = new FileOutputStream(manifestFile);
+        outstream.write(builder.toString().getBytes());
+        outstream.close();
+
+        // We want the manifest block in the archive stream to be idempotent:
+        // each time we generate a backup stream for the app, we want the manifest
+        // block to be identical.  The underlying tar mechanism sees it as a file,
+        // though, and will propagate its mtime, causing the tar header to vary.
+        // Avoid this problem by pinning the mtime to zero.
+        manifestFile.setLastModified(0);
+    }
+
     // Generic driver skeleton for full backup operations
     abstract class FullBackupTask implements Runnable {
         IFullBackupRestoreObserver mObserver;
@@ -4172,6 +4183,7 @@
         boolean mAllApps;
         boolean mIncludeSystem;
         boolean mCompress;
+        boolean mKeyValue;
         ArrayList<String> mPackages;
         PackageInfo mCurrentTarget;
         String mCurrentPassword;
@@ -4179,9 +4191,9 @@
         private final int mCurrentOpToken;
 
         PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
-                boolean includeApks, boolean includeObbs, boolean includeShared,
-                boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
-                boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) {
+                boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
+                String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
+                boolean doCompress, boolean doKeyValue, String[] packages, AtomicBoolean latch) {
             super(observer);
             mCurrentOpToken = generateToken();
             mLatch = latch;
@@ -4210,6 +4222,7 @@
                 Slog.w(TAG, "Encrypting backup with passphrase=" + mEncryptPassword);
             }
             mCompress = doCompress;
+            mKeyValue = doKeyValue;
         }
 
         void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
@@ -4309,7 +4322,8 @@
 
         @Override
         public void run() {
-            Slog.i(TAG, "--- Performing full-dataset adb backup ---");
+            String includeKeyValue = mKeyValue ? ", including key-value backups" : "";
+            Slog.i(TAG, "--- Performing adb backup" + includeKeyValue + " ---");
 
             TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<String, PackageInfo>();
             FullBackupObbConnection obbConnection = new FullBackupObbConnection();
@@ -4361,14 +4375,26 @@
 
             // Now we cull any inapplicable / inappropriate packages from the set.  This
             // includes the special shared-storage agent package; we handle that one
-            // explicitly at the end of the backup pass.
+            // explicitly at the end of the backup pass. Packages supporting key-value backup are
+            // added to their own queue, and handled after packages supporting fullbackup.
+            ArrayList<PackageInfo> keyValueBackupQueue = new ArrayList<>();
             Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
             while (iter.hasNext()) {
                 PackageInfo pkg = iter.next().getValue();
                 if (!appIsEligibleForBackup(pkg.applicationInfo)
-                        || appIsStopped(pkg.applicationInfo)
-                        || appIsKeyValueOnly(pkg)) {
+                        || appIsStopped(pkg.applicationInfo)) {
                     iter.remove();
+                    if (DEBUG) {
+                        Slog.i(TAG, "Package " + pkg.packageName
+                                + " is not eligible for backup, removing.");
+                    }
+                } else if (appIsKeyValueOnly(pkg)) {
+                    iter.remove();
+                    if (DEBUG) {
+                        Slog.i(TAG, "Package " + pkg.packageName
+                                + " is key-value.");
+                    }
+                    keyValueBackupQueue.add(pkg);
                 }
             }
 
@@ -4402,7 +4428,7 @@
                 // final '\n'.
                 //
                 // line 1: "ANDROID BACKUP"
-                // line 2: backup file format version, currently "2"
+                // line 2: backup file format version, currently "5"
                 // line 3: compressed?  "0" if not compressed, "1" if compressed.
                 // line 4: name of encryption algorithm [currently only "none" or "AES-256"]
                 //
@@ -4462,10 +4488,14 @@
                     }
                 }
 
-                // Now actually run the constructed backup sequence
+                // Now actually run the constructed backup sequence for full backup
                 int N = backupQueue.size();
                 for (int i = 0; i < N; i++) {
                     pkg = backupQueue.get(i);
+                    if (DEBUG) {
+                        Slog.i(TAG,"--- Performing full backup for package " + pkg.packageName
+                                + " ---");
+                    }
                     final boolean isSharedStorage =
                             pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
 
@@ -4485,6 +4515,21 @@
                         }
                     }
                 }
+                // And for key-value backup if enabled
+                if (mKeyValue) {
+                    for (PackageInfo keyValuePackage : keyValueBackupQueue) {
+                        if (DEBUG) {
+                            Slog.i(TAG, "--- Performing key-value backup for package "
+                                    + keyValuePackage.packageName + " ---");
+                        }
+                        KeyValueAdbBackupEngine kvBackupEngine =
+                                new KeyValueAdbBackupEngine(out, keyValuePackage,
+                                        BackupManagerService.this,
+                                        mPackageManager, mBaseStateDir, mDataDir);
+                        sendOnBackupPackage(keyValuePackage.packageName);
+                        kvBackupEngine.backupOnePackage();
+                    }
+                }
 
                 // Done!
                 finalizeBackup(out);
@@ -6693,19 +6738,24 @@
                 try {
                     // okay, presume we're okay, and extract the various metadata
                     info = new FileMetadata();
-                    info.size = extractRadix(block, 124, 12, 8);
-                    info.mtime = extractRadix(block, 136, 12, 8);
-                    info.mode = extractRadix(block, 100, 8, 8);
+                    info.size = extractRadix(block, TAR_HEADER_OFFSET_FILESIZE,
+                            TAR_HEADER_LENGTH_FILESIZE, TAR_HEADER_LONG_RADIX);
+                    info.mtime = extractRadix(block, TAR_HEADER_OFFSET_MODTIME,
+                            TAR_HEADER_LENGTH_MODTIME, TAR_HEADER_LONG_RADIX);
+                    info.mode = extractRadix(block, TAR_HEADER_OFFSET_MODE,
+                            TAR_HEADER_LENGTH_MODE, TAR_HEADER_LONG_RADIX);
 
-                    info.path = extractString(block, 345, 155); // prefix
-                    String path = extractString(block, 0, 100);
+                    info.path = extractString(block, TAR_HEADER_OFFSET_PATH_PREFIX,
+                            TAR_HEADER_LENGTH_PATH_PREFIX);
+                    String path = extractString(block, TAR_HEADER_OFFSET_PATH,
+                            TAR_HEADER_LENGTH_PATH);
                     if (path.length() > 0) {
                         if (info.path.length() > 0) info.path += '/';
                         info.path += path;
                     }
 
                     // tar link indicator field: 1 byte at offset 156 in the header.
-                    int typeChar = block[156];
+                    int typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
                     if (typeChar == 'x') {
                         // pax extended header, so we need to read that
                         gotHeader = readPaxExtendedHeader(instream, info);
@@ -6716,7 +6766,7 @@
                         }
                         if (!gotHeader) throw new IOException("Bad or missing pax header");
 
-                        typeChar = block[156];
+                        typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
                     }
 
                     switch (typeChar) {
@@ -7037,6 +7087,7 @@
         IFullBackupRestoreObserver mObserver;
         AtomicBoolean mLatchObject;
         IBackupAgent mAgent;
+        PackageManagerBackupAgent mPackageManagerBackupAgent;
         String mAgentPackage;
         ApplicationInfo mTargetApp;
         FullBackupObbConnection mObbConnection = null;
@@ -7088,6 +7139,7 @@
             mObserver = observer;
             mLatchObject = latch;
             mAgent = null;
+            mPackageManagerBackupAgent = new PackageManagerBackupAgent(mPackageManager);
             mAgentPackage = null;
             mTargetApp = null;
             mObbConnection = new FullBackupObbConnection();
@@ -7505,14 +7557,21 @@
                             long toCopy = info.size;
                             final int token = generateToken();
                             try {
-                                prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null,
+                                prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, null,
                                         OP_TYPE_RESTORE_WAIT);
-                                if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
+                                if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
                                     if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
                                             + " : " + info.path);
                                     mObbConnection.restoreObbFile(pkg, mPipes[0],
                                             info.size, info.type, info.path, info.mode,
                                             info.mtime, token, mBackupManagerBinder);
+                                } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
+                                    if (DEBUG) Slog.d(TAG, "Restoring key-value file for " + pkg
+                                            + " : " + info.path);
+                                    KeyValueAdbRestoreEngine restoreEngine =
+                                            new KeyValueAdbRestoreEngine(BackupManagerService.this,
+                                                    mDataDir, info, mPipes[0], mAgent, token);
+                                    new Thread(restoreEngine, "restore-key-value-runner").start();
                                 } else {
                                     if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
                                             + info.path);
@@ -8100,6 +8159,7 @@
                 Slog.i(TAG, b.toString());
             }
         }
+
         // Consume a tar file header block [sequence] and accumulate the relevant metadata
         FileMetadata readTarHeaders(InputStream instream) throws IOException {
             byte[] block = new byte[512];
@@ -9920,16 +9980,16 @@
         return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
     }
 
-    // Run a *full* backup pass for the given packages, writing the resulting data stream
+    // Run a backup pass for the given packages, writing the resulting data stream
     // to the supplied file descriptor.  This method is synchronous and does not return
     // to the caller until the backup has been completed.
     //
     // This is the variant used by 'adb backup'; it requires on-screen confirmation
     // by the user because it can be used to offload data over untrusted USB.
-    public void fullBackup(ParcelFileDescriptor fd, boolean includeApks,
-            boolean includeObbs, boolean includeShared, boolean doWidgets,
-            boolean doAllApps, boolean includeSystem, boolean compress, String[] pkgList) {
-        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
+    public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+            boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
+            boolean compress, boolean doKeyValue, String[] pkgList) {
+        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
 
         final int callingUserHandle = UserHandle.getCallingUserId();
         // TODO: http://b/22388012
@@ -9954,27 +10014,28 @@
         try {
             // Doesn't make sense to do a full backup prior to setup
             if (!deviceIsProvisioned()) {
-                Slog.i(TAG, "Full backup not supported before setup");
+                Slog.i(TAG, "Backup not supported before setup");
                 return;
             }
 
-            if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks
-                    + " obb=" + includeObbs + " shared=" + includeShared + " all=" + doAllApps
-                    + " system=" + includeSystem + " pkgs=" + pkgList);
-            Slog.i(TAG, "Beginning full backup...");
+            if (DEBUG) Slog.v(TAG, "Requesting backup: apks=" + includeApks + " obb=" + includeObbs
+                    + " shared=" + includeShared + " all=" + doAllApps + " system="
+                    + includeSystem + " includekeyvalue=" + doKeyValue + " pkgs=" + pkgList);
+            Slog.i(TAG, "Beginning adb backup...");
 
-            FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs,
-                    includeShared, doWidgets, doAllApps, includeSystem, compress, pkgList);
+            AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
+                    includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
+                    pkgList);
             final int token = generateToken();
-            synchronized (mFullConfirmations) {
-                mFullConfirmations.put(token, params);
+            synchronized (mAdbBackupRestoreConfirmations) {
+                mAdbBackupRestoreConfirmations.put(token, params);
             }
 
             // start up the confirmation UI
             if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token);
             if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
-                Slog.e(TAG, "Unable to launch full backup confirmation");
-                mFullConfirmations.delete(token);
+                Slog.e(TAG, "Unable to launch backup confirmation UI");
+                mAdbBackupRestoreConfirmations.delete(token);
                 return;
             }
 
@@ -9987,7 +10048,7 @@
             startConfirmationTimeout(token, params);
 
             // wait for the backup to be performed
-            if (DEBUG) Slog.d(TAG, "Waiting for full backup completion...");
+            if (DEBUG) Slog.d(TAG, "Waiting for backup completion...");
             waitForCompletion(params);
         } finally {
             try {
@@ -9996,7 +10057,7 @@
                 // just eat it
             }
             Binder.restoreCallingIdentity(oldId);
-            Slog.d(TAG, "Full backup processing complete.");
+            Slog.d(TAG, "Adb backup processing complete.");
         }
     }
 
@@ -10049,8 +10110,8 @@
         }
     }
 
-    public void fullRestore(ParcelFileDescriptor fd) {
-        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore");
+    public void adbRestore(ParcelFileDescriptor fd) {
+        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
 
         final int callingUserHandle = UserHandle.getCallingUserId();
         // TODO: http://b/22388012
@@ -10068,19 +10129,19 @@
                 return;
             }
 
-            Slog.i(TAG, "Beginning full restore...");
+            Slog.i(TAG, "Beginning restore...");
 
-            FullRestoreParams params = new FullRestoreParams(fd);
+            AdbRestoreParams params = new AdbRestoreParams(fd);
             final int token = generateToken();
-            synchronized (mFullConfirmations) {
-                mFullConfirmations.put(token, params);
+            synchronized (mAdbBackupRestoreConfirmations) {
+                mAdbBackupRestoreConfirmations.put(token, params);
             }
 
             // start up the confirmation UI
             if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token);
             if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) {
-                Slog.e(TAG, "Unable to launch full restore confirmation");
-                mFullConfirmations.delete(token);
+                Slog.e(TAG, "Unable to launch restore confirmation");
+                mAdbBackupRestoreConfirmations.delete(token);
                 return;
             }
 
@@ -10093,16 +10154,16 @@
             startConfirmationTimeout(token, params);
 
             // wait for the restore to be performed
-            if (DEBUG) Slog.d(TAG, "Waiting for full restore completion...");
+            if (DEBUG) Slog.d(TAG, "Waiting for restore completion...");
             waitForCompletion(params);
         } finally {
             try {
                 fd.close();
             } catch (IOException e) {
-                Slog.w(TAG, "Error trying to close fd after full restore: " + e);
+                Slog.w(TAG, "Error trying to close fd after adb restore: " + e);
             }
             Binder.restoreCallingIdentity(oldId);
-            Slog.i(TAG, "Full restore processing complete.");
+            Slog.i(TAG, "adb restore processing complete.");
         }
     }
 
@@ -10120,7 +10181,7 @@
         return true;
     }
 
-    void startConfirmationTimeout(int token, FullParams params) {
+    void startConfirmationTimeout(int token, AdbParams params) {
         if (MORE_DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
                 + TIMEOUT_FULL_CONFIRMATION + " millis");
         Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
@@ -10128,7 +10189,7 @@
         mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
     }
 
-    void waitForCompletion(FullParams params) {
+    void waitForCompletion(AdbParams params) {
         synchronized (params.latch) {
             while (params.latch.get() == false) {
                 try {
@@ -10138,7 +10199,7 @@
         }
     }
 
-    void signalFullBackupRestoreCompletion(FullParams params) {
+    void signalAdbBackupRestoreCompletion(AdbParams params) {
         synchronized (params.latch) {
             params.latch.set(true);
             params.latch.notifyAll();
@@ -10147,27 +10208,27 @@
 
     // Confirm that the previously-requested full backup/restore operation can proceed.  This
     // is used to require a user-facing disclosure about the operation.
-    public void acknowledgeFullBackupOrRestore(int token, boolean allow,
+    public void acknowledgeAdbBackupOrRestore(int token, boolean allow,
             String curPassword, String encPpassword, IFullBackupRestoreObserver observer) {
-        if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : token=" + token
+        if (DEBUG) Slog.d(TAG, "acknowledgeAdbBackupOrRestore : token=" + token
                 + " allow=" + allow);
 
         // TODO: possibly require not just this signature-only permission, but even
         // require that the specific designated confirmation-UI app uid is the caller?
-        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeFullBackupOrRestore");
+        mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeAdbBackupOrRestore");
 
         long oldId = Binder.clearCallingIdentity();
         try {
 
-            FullParams params;
-            synchronized (mFullConfirmations) {
-                params = mFullConfirmations.get(token);
+            AdbParams params;
+            synchronized (mAdbBackupRestoreConfirmations) {
+                params = mAdbBackupRestoreConfirmations.get(token);
                 if (params != null) {
                     mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params);
-                    mFullConfirmations.delete(token);
+                    mAdbBackupRestoreConfirmations.delete(token);
 
                     if (allow) {
-                        final int verb = params instanceof FullBackupParams
+                        final int verb = params instanceof AdbBackupParams
                                 ? MSG_RUN_ADB_BACKUP
                                 : MSG_RUN_ADB_RESTORE;
 
@@ -10183,7 +10244,7 @@
                     } else {
                         Slog.w(TAG, "User rejected full backup/restore operation");
                         // indicate completion without having actually transferred any data
-                        signalFullBackupRestoreCompletion(params);
+                        signalAdbBackupRestoreCompletion(params);
                     }
                 } else {
                     Slog.w(TAG, "Attempted to ack full backup/restore with invalid token");
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
new file mode 100644
index 0000000..cd13760
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -0,0 +1,281 @@
+package com.android.server.backup;
+
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;
+
+import android.app.ApplicationThreadConstants;
+import android.app.IBackupAgent;
+import android.app.backup.FullBackup;
+import android.app.backup.FullBackupDataOutput;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Used by BackupManagerService to perform adb backup for key-value packages. At the moment this
+ * class resembles what is done in the standard key-value code paths in BackupManagerService, and
+ * should be unified later.
+ *
+ * TODO: We should create unified backup/restore engines that can be used for both transport and
+ * adb backup/restore, and for fullbackup and key-value backup.
+ */
+class KeyValueAdbBackupEngine {
+    private static final String TAG = "KeyValueAdbBackupEngine";
+    private static final boolean DEBUG = false;
+
+    private static final String BACKUP_KEY_VALUE_DIRECTORY_NAME = "key_value_dir";
+    private static final String BACKUP_KEY_VALUE_BLANK_STATE_FILENAME = "blank_state";
+    private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data";
+    private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new";
+
+    private BackupManagerService mBackupManagerService;
+    private final PackageManager mPackageManager;
+    private final OutputStream mOutput;
+    private final PackageInfo mCurrentPackage;
+    private final File mDataDir;
+    private final File mStateDir;
+    private final File mBlankStateName;
+    private final File mBackupDataName;
+    private final File mNewStateName;
+    private final File mManifestFile;
+    private ParcelFileDescriptor mSavedState;
+    private ParcelFileDescriptor mBackupData;
+    private ParcelFileDescriptor mNewState;
+
+    KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo,
+            BackupManagerService backupManagerService, PackageManager packageManager,
+            File baseStateDir, File dataDir) {
+        mOutput = output;
+        mCurrentPackage = packageInfo;
+        mBackupManagerService = backupManagerService;
+        mPackageManager = packageManager;
+
+        mDataDir = dataDir;
+        mStateDir = new File(baseStateDir, BACKUP_KEY_VALUE_DIRECTORY_NAME);
+        mStateDir.mkdirs();
+
+        String pkg = mCurrentPackage.packageName;
+
+        mBlankStateName = new File(mStateDir, BACKUP_KEY_VALUE_BLANK_STATE_FILENAME);
+        mBackupDataName = new File(mDataDir,
+                pkg + BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX);
+        mNewStateName = new File(mStateDir,
+                pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
+
+        mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
+    }
+
+    void backupOnePackage() throws IOException {
+        ApplicationInfo targetApp = mCurrentPackage.applicationInfo;
+
+        try {
+            prepareBackupFiles(mCurrentPackage.packageName);
+
+            IBackupAgent agent = bindToAgent(targetApp);
+
+            if (agent == null) {
+                // We failed binding to the agent, so ignore this package
+                Slog.e(TAG, "Failed binding to BackupAgent for package "
+                        + mCurrentPackage.packageName);
+                return;
+            }
+
+            // We are bound to agent, initiate backup.
+            if (!invokeAgentForAdbBackup(mCurrentPackage.packageName, agent)) {
+                // Backup failed, skip package.
+                Slog.e(TAG, "Backup Failed for package " + mCurrentPackage.packageName);
+                return;
+            }
+
+            // Backup finished successfully. Copy the backup data to the output stream.
+            writeBackupData();
+        } catch (FileNotFoundException e) {
+            Slog.e(TAG, "Failed creating files for package " + mCurrentPackage.packageName
+                    + " will ignore package. " + e);
+        } finally {
+            // We are either done, failed or have timed out, so do cleanup and kill the agent.
+            cleanup();
+        }
+    }
+
+    private void  prepareBackupFiles(String packageName) throws FileNotFoundException {
+
+        // We pass a blank state to make sure we are getting the complete backup, not just an
+        // increment
+        mSavedState = ParcelFileDescriptor.open(mBlankStateName,
+                MODE_READ_ONLY | MODE_CREATE);  // Make an empty file if necessary
+
+        mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+                MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+
+        if (!SELinux.restorecon(mBackupDataName)) {
+            Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName);
+        }
+
+        mNewState = ParcelFileDescriptor.open(mNewStateName,
+                MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+    }
+
+    private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
+        try {
+            return mBackupManagerService.bindToAgentSynchronous(targetApp,
+                    ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+        } catch (SecurityException e) {
+            Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
+                    + ". " + e);
+            return null;
+        }
+    }
+
+    // Return true on backup success, false otherwise
+    private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) {
+        int token = mBackupManagerService.generateToken();
+        try {
+            mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
+                    OP_TYPE_BACKUP_WAIT);
+
+            // Start backup and wait for BackupManagerService to get callback for success or timeout
+            agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
+                    mBackupManagerService.mBackupManagerBinder);
+            if (!mBackupManagerService.waitUntilOperationComplete(token)) {
+                Slog.e(TAG, "Key-value backup failed on package " + packageName);
+                return false;
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "Key-value backup success for package " + packageName);
+            }
+            return true;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error invoking agent for backup on " + packageName + ". " + e);
+            return false;
+        }
+    }
+
+    class KeyValueAdbBackupDataCopier implements Runnable {
+        private final PackageInfo mPackage;
+        private final ParcelFileDescriptor mPipe;
+        private final int mToken;
+
+        KeyValueAdbBackupDataCopier(PackageInfo pack, ParcelFileDescriptor pipe,
+                int token)
+                throws IOException {
+            mPackage = pack;
+            mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
+            mToken = token;
+        }
+
+        @Override
+        public void run() {
+            try {
+                FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
+
+                if (DEBUG) {
+                    Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
+                }
+                BackupManagerService.writeAppManifest(
+                        mPackage, mPackageManager, mManifestFile, false, false);
+                FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
+                        mDataDir.getAbsolutePath(),
+                        mManifestFile.getAbsolutePath(),
+                        output);
+                mManifestFile.delete();
+
+                if (DEBUG) {
+                    Slog.d(TAG, "Writing key-value package payload" + mPackage.packageName);
+                }
+                FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
+                        mDataDir.getAbsolutePath(),
+                        mBackupDataName.getAbsolutePath(),
+                        output);
+
+                // Write EOD marker
+                try {
+                    FileOutputStream out = new FileOutputStream(mPipe.getFileDescriptor());
+                    byte[] buf = new byte[4];
+                    out.write(buf);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Unable to finalize backup stream!");
+                }
+
+                try {
+                    mBackupManagerService.mBackupManagerBinder.opComplete(mToken, 0);
+                } catch (RemoteException e) {
+                    // we'll time out anyway, so we're safe
+                }
+
+            } catch (IOException e) {
+                Slog.e(TAG, "Error running full backup for " + mPackage.packageName + ". " + e);
+            } finally {
+                IoUtils.closeQuietly(mPipe);
+            }
+        }
+    }
+
+    private void writeBackupData() throws IOException {
+
+        int token = mBackupManagerService.generateToken();
+
+        ParcelFileDescriptor[] pipes = null;
+        try {
+            pipes = ParcelFileDescriptor.createPipe();
+
+            mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
+                    OP_TYPE_BACKUP_WAIT);
+
+            // We will have to create a runnable that will read the manifest and backup data we
+            // created, such that we can pipe the data into mOutput. The reason we do this is that
+            // internally FullBackup.backupToTar is used, which will create the necessary file
+            // header, but will also chunk the data. The method routeSocketDataToOutput in
+            // BackupManagerService will dechunk the data, and append it to the TAR outputstream.
+            KeyValueAdbBackupDataCopier runner = new KeyValueAdbBackupDataCopier(mCurrentPackage, pipes[1],
+                    token);
+            pipes[1].close();   // the runner has dup'd it
+            pipes[1] = null;
+            Thread t = new Thread(runner, "key-value-app-data-runner");
+            t.start();
+
+            // Now pull data from the app and stuff it into the output
+            BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);
+
+            if (!mBackupManagerService.waitUntilOperationComplete(token)) {
+                Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
+            } else {
+                if (DEBUG) {
+                    Slog.d(TAG, "Full package backup success: " + mCurrentPackage.packageName);
+                }
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Error backing up " + mCurrentPackage.packageName + ": " + e);
+        } finally {
+            // flush after every package
+            mOutput.flush();
+            if (pipes != null) {
+                IoUtils.closeQuietly(pipes[0]);
+                IoUtils.closeQuietly(pipes[1]);
+            }
+        }
+    }
+
+    private void cleanup() {
+        mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
+        mBlankStateName.delete();
+        mNewStateName.delete();
+        mBackupDataName.delete();
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
new file mode 100644
index 0000000..6fb9355
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -0,0 +1,148 @@
+package com.android.server.backup;
+
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+
+import android.app.IBackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackup;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.backup.BackupManagerService.FileMetadata;
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Used by BackupManagerService to perform adb restore for key-value packages. At the moment this
+ * class resembles what is done in the standard key-value code paths in BackupManagerService, and
+ * should be unified later.
+ *
+ * TODO: We should create unified backup/restore engines that can be used for both transport and
+ * adb backup/restore, and for fullbackup and key-value backup.
+ */
+class KeyValueAdbRestoreEngine implements Runnable {
+    private static final String TAG = "KeyValueAdbRestoreEngine";
+    private static final boolean DEBUG = false;
+
+    private final BackupManagerService mBackupManagerService;
+    private final File mDataDir;
+
+    FileMetadata mInfo;
+    BackupManagerService.PerformAdbRestoreTask mRestoreTask;
+    ParcelFileDescriptor mInFD;
+    IBackupAgent mAgent;
+    int mToken;
+
+    KeyValueAdbRestoreEngine(BackupManagerService backupManagerService, File dataDir,
+            FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent, int token) {
+        mBackupManagerService = backupManagerService;
+        mDataDir = dataDir;
+        mInfo = info;
+        mInFD = inFD;
+        mAgent = agent;
+        mToken = token;
+    }
+
+    @Override
+    public void run() {
+        try {
+            File restoreData = prepareRestoreData(mInfo, mInFD);
+
+            // TODO: version ?
+            invokeAgentForAdbRestore(mAgent, mInfo, restoreData, 0);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private File prepareRestoreData(FileMetadata info, ParcelFileDescriptor inFD) throws IOException {
+        String pkg = info.packageName;
+        File restoreDataName = new File(mDataDir, pkg + ".restore");
+        File sortedDataName = new File(mDataDir, pkg + ".sorted");
+
+        FullBackup.restoreFile(inFD, info.size, info.type, info.mode, info.mtime, restoreDataName);
+
+        // Sort the keys, as the BackupAgent expect them to come in lexicographical order
+        sortKeyValueData(restoreDataName, sortedDataName);
+        return sortedDataName;
+    }
+
+    private void invokeAgentForAdbRestore(IBackupAgent agent, FileMetadata info, File restoreData,
+            int versionCode) throws IOException {
+        String pkg = info.packageName;
+        File newStateName = new File(mDataDir, pkg + ".new");
+        try {
+            ParcelFileDescriptor backupData =
+                    ParcelFileDescriptor.open(restoreData, MODE_READ_ONLY);
+            ParcelFileDescriptor newState = ParcelFileDescriptor.open(newStateName,
+                    MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+
+            if (DEBUG) {
+                Slog.i(TAG, "Starting restore of package " + pkg + " for version code "
+                        + versionCode);
+            }
+            agent.doRestore(backupData, versionCode, newState, mToken,
+                    mBackupManagerService.mBackupManagerBinder);
+        } catch (IOException e) {
+            Slog.e(TAG, "Exception opening file. " + e);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Exception calling doRestore on agent: " + e);
+        }
+    }
+
+    private void sortKeyValueData (File restoreData, File sortedData) throws IOException {
+        FileInputStream inputStream = null;
+        FileOutputStream outputStream = null;
+        try {
+            inputStream = new FileInputStream(restoreData);
+            outputStream = new FileOutputStream(sortedData);
+            BackupDataInput reader = new BackupDataInput(inputStream.getFD());
+            BackupDataOutput writer = new BackupDataOutput(outputStream.getFD());
+            copyKeysInLexicalOrder(reader, writer);
+        } finally {
+            if (inputStream != null) {
+                IoUtils.closeQuietly(inputStream);
+            }
+            if (outputStream != null) {
+                IoUtils.closeQuietly(outputStream);
+            }
+        }
+    }
+
+    private void copyKeysInLexicalOrder(BackupDataInput in, BackupDataOutput out)
+            throws IOException {
+        Map<String, byte[]> data = new HashMap<>();
+        while (in.readNextHeader()) {
+            String key = in.getKey();
+            int size = in.getDataSize();
+            if (size < 0) {
+                in.skipEntityData();
+                continue;
+            }
+            byte[] value = new byte[size];
+            in.readEntityData(value, 0, size);
+            data.put(key, value);
+        }
+        List<String> keys = new ArrayList<>(data.keySet());
+        Collections.sort(keys);
+        for (String key : keys) {
+            byte[] value = data.get(key);
+            out.writeEntityHeader(key, value.length);
+            out.writeEntityData(value, value.length);
+        }
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 8855661..c40f2ca 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -227,14 +227,14 @@
     }
 
     @Override
-    public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+    public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
             boolean includeShared, boolean doWidgets, boolean allApps,
-            boolean allIncludesSystem, boolean doCompress, String[] packageNames)
+            boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames)
                     throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.fullBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
-                    allApps, allIncludesSystem, doCompress, packageNames);
+            svc.adbBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
+                    allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
         }
     }
 
@@ -247,10 +247,10 @@
     }
 
     @Override
-    public void fullRestore(ParcelFileDescriptor fd) throws RemoteException {
+    public void adbRestore(ParcelFileDescriptor fd) throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.fullRestore(fd);
+            svc.adbRestore(fd);
         }
     }
 
@@ -260,7 +260,7 @@
                     throws RemoteException {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.acknowledgeFullBackupOrRestore(token, allow,
+            svc.acknowledgeAdbBackupOrRestore(token, allow,
                     curPassword, encryptionPassword, observer);
         }
     }
diff --git a/services/core/Android.mk b/services/core/Android.mk
index e35a171..099f557 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -22,7 +22,8 @@
     services.net \
     android.hardware.light@2.0-java \
     android.hardware.power@1.0-java \
-    android.hardware.tv.cec@1.0-java
+    android.hardware.tv.cec@1.0-java \
+    android.hidl.manager@1.0-java
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     tzdata_shared2 \
diff --git a/services/core/java/com/android/server/NetworkManagementInternal.java b/services/core/java/com/android/server/NetworkManagementInternal.java
new file mode 100644
index 0000000..f53c454
--- /dev/null
+++ b/services/core/java/com/android/server/NetworkManagementInternal.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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.server;
+
+/**
+ * NetworkManagement local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class NetworkManagementInternal {
+    /**
+     * Checks if network is restricted for {@param uid} as per the app idle state, device idle mode,
+     * battery and data saver modes.
+     */
+    public abstract boolean isNetworkRestrictedForUid(int uid);
+}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index adc5e33..74328c0 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -27,7 +27,9 @@
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
 import static android.net.NetworkPolicyManager.FIREWALL_TYPE_BLACKLIST;
 import static android.net.NetworkPolicyManager.FIREWALL_TYPE_WHITELIST;
 import static android.net.NetworkStats.SET_DEFAULT;
@@ -90,6 +92,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.net.NetworkStatsFactory;
 import com.android.internal.util.HexDump;
@@ -222,7 +225,12 @@
 
     private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
 
+    /**
+     * If both locks need to be held, then they should be obtained in the order:
+     * first {@link #mQuotaLock} and then {@link #mRulesLock}.
+     */
     private Object mQuotaLock = new Object();
+    private Object mRulesLock = new Object();
 
     /** Set of interfaces with active quotas. */
     @GuardedBy("mQuotaLock")
@@ -231,41 +239,41 @@
     @GuardedBy("mQuotaLock")
     private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
     /** Set of UIDs blacklisted on metered networks. */
-    @GuardedBy("mQuotaLock")
+    @GuardedBy("mRulesLock")
     private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray();
     /** Set of UIDs whitelisted on metered networks. */
-    @GuardedBy("mQuotaLock")
+    @GuardedBy("mRulesLock")
     private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray();
     /** Set of UIDs with cleartext penalties. */
     @GuardedBy("mQuotaLock")
     private SparseIntArray mUidCleartextPolicy = new SparseIntArray();
     /** Set of UIDs that are to be blocked/allowed by firewall controller. */
-    @GuardedBy("mQuotaLock")
+    @GuardedBy("mRulesLock")
     private SparseIntArray mUidFirewallRules = new SparseIntArray();
     /**
      * Set of UIDs that are to be blocked/allowed by firewall controller.  This set of Ids matches
      * to application idles.
      */
-    @GuardedBy("mQuotaLock")
+    @GuardedBy("mRulesLock")
     private SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
     /**
      * Set of UIDs that are to be blocked/allowed by firewall controller.  This set of Ids matches
      * to device idles.
      */
-    @GuardedBy("mQuotaLock")
+    @GuardedBy("mRulesLock")
     private SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
     /**
      * Set of UIDs that are to be blocked/allowed by firewall controller.  This set of Ids matches
      * to device on power-save mode.
      */
-    @GuardedBy("mQuotaLock")
+    @GuardedBy("mRulesLock")
     private SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
     /** Set of states for the child firewall chains. True if the chain is active. */
-    @GuardedBy("mQuotaLock")
+    @GuardedBy("mRulesLock")
     final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
 
     @GuardedBy("mQuotaLock")
-    private boolean mDataSaverMode;
+    private volatile boolean mDataSaverMode;
 
     private Object mIdleTimerLock = new Object();
     /** Set of interfaces with active idle timers. */
@@ -321,6 +329,17 @@
 
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
+
+        LocalServices.addService(NetworkManagementInternal.class, new LocalService());
+    }
+
+    @VisibleForTesting
+    NetworkManagementService() {
+        mConnector = null;
+        mContext = null;
+        mDaemonHandler = null;
+        mFgHandler = null;
+        mThread = null;
     }
 
     static NetworkManagementService create(Context context, String socket)
@@ -502,21 +521,24 @@
     }
 
     // Sync the state of the given chain with the native daemon.
-    private void syncFirewallChainLocked(int chain, SparseIntArray uidFirewallRules, String name) {
-        int size = uidFirewallRules.size();
-        if (size > 0) {
+    private void syncFirewallChainLocked(int chain, String name) {
+        SparseIntArray rules;
+        synchronized (mRulesLock) {
+            final SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
             // Make a copy of the current rules, and then clear them. This is because
-            // setFirewallUidRuleInternal only pushes down rules to the native daemon if they are
-            // different from the current rules stored in the mUidFirewall*Rules array for the
-            // specified chain. If we don't clear the rules, setFirewallUidRuleInternal will do
-            // nothing.
-            final SparseIntArray rules = uidFirewallRules.clone();
+            // setFirewallUidRuleInternal only pushes down rules to the native daemon if they
+            // are different from the current rules stored in the mUidFirewall*Rules array for
+            // the specified chain. If we don't clear the rules, setFirewallUidRuleInternal
+            // will do nothing.
+            rules = uidFirewallRules.clone();
             uidFirewallRules.clear();
-
+        }
+        if (rules.size() > 0) {
             // Now push the rules. setFirewallUidRuleInternal will push each of these down to the
             // native daemon, and also add them to the mUidFirewall*Rules array for the specified
             // chain.
-            if (DBG) Slog.d(TAG, "Pushing " + size + " active firewall " + name + "UID rules");
+            if (DBG) Slog.d(TAG, "Pushing " + rules.size() + " active firewall "
+                    + name + "UID rules");
             for (int i = 0; i < rules.size(); i++) {
                 setFirewallUidRuleLocked(chain, rules.keyAt(i), rules.valueAt(i));
             }
@@ -597,22 +619,30 @@
                 }
             }
 
-            size = mUidRejectOnMetered.size();
-            if (size > 0) {
-                if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
-                final SparseBooleanArray uidRejectOnQuota = mUidRejectOnMetered;
-                mUidRejectOnMetered = new SparseBooleanArray();
+            SparseBooleanArray uidRejectOnQuota = null;
+            SparseBooleanArray uidAcceptOnQuota = null;
+            synchronized (mRulesLock) {
+                size = mUidRejectOnMetered.size();
+                if (size > 0) {
+                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
+                    uidRejectOnQuota = mUidRejectOnMetered;
+                    mUidRejectOnMetered = new SparseBooleanArray();
+                }
+
+                size = mUidAllowOnMetered.size();
+                if (size > 0) {
+                    if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
+                    uidAcceptOnQuota = mUidAllowOnMetered;
+                    mUidAllowOnMetered = new SparseBooleanArray();
+                }
+            }
+            if (uidRejectOnQuota != null) {
                 for (int i = 0; i < uidRejectOnQuota.size(); i++) {
                     setUidMeteredNetworkBlacklist(uidRejectOnQuota.keyAt(i),
                             uidRejectOnQuota.valueAt(i));
                 }
             }
-
-            size = mUidAllowOnMetered.size();
-            if (size > 0) {
-                if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
-                final SparseBooleanArray uidAcceptOnQuota = mUidAllowOnMetered;
-                mUidAllowOnMetered = new SparseBooleanArray();
+            if (uidAcceptOnQuota != null) {
                 for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
                     setUidMeteredNetworkWhitelist(uidAcceptOnQuota.keyAt(i),
                             uidAcceptOnQuota.valueAt(i));
@@ -631,20 +661,17 @@
 
             setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
 
-            syncFirewallChainLocked(FIREWALL_CHAIN_NONE, mUidFirewallRules, "");
-            syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, mUidFirewallStandbyRules, "standby ");
-            syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, mUidFirewallDozableRules, "dozable ");
-            syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, mUidFirewallPowerSaveRules,
-                    "powersave ");
+            syncFirewallChainLocked(FIREWALL_CHAIN_NONE, "");
+            syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, "standby ");
+            syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable ");
+            syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
 
-            if (mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY)) {
-                setFirewallChainEnabled(FIREWALL_CHAIN_STANDBY, true);
-            }
-            if (mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE)) {
-                setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, true);
-            }
-            if (mFirewallChainStates.get(FIREWALL_CHAIN_POWERSAVE)) {
-                setFirewallChainEnabled(FIREWALL_CHAIN_POWERSAVE, true);
+            final int[] chains =
+                    {FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE};
+            for (int chain : chains) {
+                if (getFirewallChainState(chain)) {
+                    setFirewallChainEnabled(chain, true);
+                }
             }
         }
     }
@@ -1602,8 +1629,7 @@
         }
     }
 
-    private void setUidOnMeteredNetworkList(SparseBooleanArray quotaList, int uid,
-            boolean blacklist, boolean enable) {
+    private void setUidOnMeteredNetworkList(int uid, boolean blacklist, boolean enable) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
         // silently discard when control disabled
@@ -1614,7 +1640,12 @@
         final String suffix = enable ? "add" : "remove";
 
         synchronized (mQuotaLock) {
-            final boolean oldEnable = quotaList.get(uid, false);
+            boolean oldEnable;
+            SparseBooleanArray quotaList;
+            synchronized (mRulesLock) {
+                quotaList = blacklist ? mUidRejectOnMetered : mUidAllowOnMetered;
+                oldEnable = quotaList.get(uid, false);
+            }
             if (oldEnable == enable) {
                 // TODO: eventually consider throwing
                 return;
@@ -1623,10 +1654,12 @@
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
             try {
                 mConnector.execute("bandwidth", suffix + chain, uid);
-                if (enable) {
-                    quotaList.put(uid, true);
-                } else {
-                    quotaList.delete(uid);
+                synchronized (mRulesLock) {
+                    if (enable) {
+                        quotaList.put(uid, true);
+                    } else {
+                        quotaList.delete(uid);
+                    }
                 }
             } catch (NativeDaemonConnectorException e) {
                 throw e.rethrowAsParcelableException();
@@ -1638,12 +1671,12 @@
 
     @Override
     public void setUidMeteredNetworkBlacklist(int uid, boolean enable) {
-        setUidOnMeteredNetworkList(mUidRejectOnMetered, uid, true, enable);
+        setUidOnMeteredNetworkList(uid, true, enable);
     }
 
     @Override
     public void setUidMeteredNetworkWhitelist(int uid, boolean enable) {
-        setUidOnMeteredNetworkList(mUidAllowOnMetered, uid, false, enable);
+        setUidOnMeteredNetworkList(uid, false, enable);
     }
 
     @Override
@@ -1934,7 +1967,6 @@
         // UID ranges whose sockets we won't touch.
         int[] exemptUids;
 
-        final SparseIntArray rules = getUidFirewallRules(chain);
         int numUids = 0;
 
         if (getFirewallType(chain) == FIREWALL_TYPE_WHITELIST) {
@@ -1945,11 +1977,14 @@
                 new UidRange(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE),
             };
             // ... except for the UIDs that have allow rules.
-            exemptUids = new int[rules.size()];
-            for (int i = 0; i < exemptUids.length; i++) {
-                if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) {
-                    exemptUids[numUids] = rules.keyAt(i);
-                    numUids++;
+            synchronized (mRulesLock) {
+                final SparseIntArray rules = getUidFirewallRulesLR(chain);
+                exemptUids = new int[rules.size()];
+                for (int i = 0; i < exemptUids.length; i++) {
+                    if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) {
+                        exemptUids[numUids] = rules.keyAt(i);
+                        numUids++;
+                    }
                 }
             }
             // Normally, whitelist chains only contain deny rules, so numUids == exemptUids.length.
@@ -1964,12 +1999,15 @@
             }
         } else {
             // Close sockets for every UID that has a deny rule...
-            ranges = new UidRange[rules.size()];
-            for (int i = 0; i < ranges.length; i++) {
-                if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) {
-                    int uid = rules.keyAt(i);
-                    ranges[numUids] = new UidRange(uid, uid);
-                    numUids++;
+            synchronized (mRulesLock) {
+                final SparseIntArray rules = getUidFirewallRulesLR(chain);
+                ranges = new UidRange[rules.size()];
+                for (int i = 0; i < ranges.length; i++) {
+                    if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) {
+                        int uid = rules.keyAt(i);
+                        ranges[numUids] = new UidRange(uid, uid);
+                        numUids++;
+                    }
                 }
             }
             // As above; usually numUids == ranges.length, but not always.
@@ -1991,12 +2029,14 @@
     public void setFirewallChainEnabled(int chain, boolean enable) {
         enforceSystemUid();
         synchronized (mQuotaLock) {
-            if (mFirewallChainStates.get(chain) == enable) {
-                // All is the same, nothing to do.  This relies on the fact that netd has child
-                // chains default detached.
-                return;
+            synchronized (mRulesLock) {
+                if (getFirewallChainState(chain) == enable) {
+                    // All is the same, nothing to do.  This relies on the fact that netd has child
+                    // chains default detached.
+                    return;
+                }
+                setFirewallChainState(chain, enable);
             }
-            mFirewallChainStates.put(chain, enable);
 
             final String operation = enable ? "enable_chain" : "disable_chain";
             final String chainName;
@@ -2048,27 +2088,29 @@
     public void setFirewallUidRules(int chain, int[] uids, int[] rules) {
         enforceSystemUid();
         synchronized (mQuotaLock) {
-            SparseIntArray uidFirewallRules = getUidFirewallRules(chain);
-            SparseIntArray newRules = new SparseIntArray();
-            // apply new set of rules
-            for (int index = uids.length - 1; index >= 0; --index) {
-                int uid = uids[index];
-                int rule = rules[index];
-                updateFirewallUidRuleLocked(chain, uid, rule);
-                newRules.put(uid, rule);
-            }
-            // collect the rules to remove.
-            SparseIntArray rulesToRemove = new SparseIntArray();
-            for (int index = uidFirewallRules.size() - 1; index >= 0; --index) {
-                int uid = uidFirewallRules.keyAt(index);
-                if (newRules.indexOfKey(uid) < 0) {
-                    rulesToRemove.put(uid, FIREWALL_RULE_DEFAULT);
+            synchronized (mRulesLock) {
+                SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
+                SparseIntArray newRules = new SparseIntArray();
+                // apply new set of rules
+                for (int index = uids.length - 1; index >= 0; --index) {
+                    int uid = uids[index];
+                    int rule = rules[index];
+                    updateFirewallUidRuleLocked(chain, uid, rule);
+                    newRules.put(uid, rule);
                 }
-            }
-            // remove dead rules
-            for (int index = rulesToRemove.size() - 1; index >= 0; --index) {
-                int uid = rulesToRemove.keyAt(index);
-                updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
+                // collect the rules to remove.
+                SparseIntArray rulesToRemove = new SparseIntArray();
+                for (int index = uidFirewallRules.size() - 1; index >= 0; --index) {
+                    int uid = uidFirewallRules.keyAt(index);
+                    if (newRules.indexOfKey(uid) < 0) {
+                        rulesToRemove.put(uid, FIREWALL_RULE_DEFAULT);
+                    }
+                }
+                // remove dead rules
+                for (int index = rulesToRemove.size() - 1; index >= 0; --index) {
+                    int uid = rulesToRemove.keyAt(index);
+                    updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
+                }
             }
             try {
                 switch (chain) {
@@ -2112,28 +2154,30 @@
 
     // TODO: now that netd supports batching, NMS should not keep these data structures anymore...
     private boolean updateFirewallUidRuleLocked(int chain, int uid, int rule) {
-        SparseIntArray uidFirewallRules = getUidFirewallRules(chain);
+        synchronized (mRulesLock) {
+            SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
 
-        final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT);
-        if (DBG) {
-            Slog.d(TAG, "oldRule = " + oldUidFirewallRule
-                    + ", newRule=" + rule + " for uid=" + uid + " on chain " + chain);
-        }
-        if (oldUidFirewallRule == rule) {
-            if (DBG) Slog.d(TAG, "!!!!! Skipping change");
-            // TODO: eventually consider throwing
-            return false;
-        }
+            final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT);
+            if (DBG) {
+                Slog.d(TAG, "oldRule = " + oldUidFirewallRule
+                        + ", newRule=" + rule + " for uid=" + uid + " on chain " + chain);
+            }
+            if (oldUidFirewallRule == rule) {
+                if (DBG) Slog.d(TAG, "!!!!! Skipping change");
+                // TODO: eventually consider throwing
+                return false;
+            }
 
-        String ruleName = getFirewallRuleName(chain, rule);
-        String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule);
+            String ruleName = getFirewallRuleName(chain, rule);
+            String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule);
 
-        if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
-            uidFirewallRules.delete(uid);
-        } else {
-            uidFirewallRules.put(uid, rule);
+            if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
+                uidFirewallRules.delete(uid);
+            } else {
+                uidFirewallRules.put(uid, rule);
+            }
+            return !ruleName.equals(oldRuleName);
         }
-        return !ruleName.equals(oldRuleName);
     }
 
     private @NonNull String getFirewallRuleName(int chain, int rule) {
@@ -2154,7 +2198,7 @@
         return ruleName;
     }
 
-    private @NonNull SparseIntArray getUidFirewallRules(int chain) {
+    private @NonNull SparseIntArray getUidFirewallRulesLR(int chain) {
         switch (chain) {
             case FIREWALL_CHAIN_STANDBY:
                 return mUidFirewallStandbyRules;
@@ -2284,29 +2328,25 @@
             pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString());
             pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
             pw.print("Data saver mode: "); pw.println(mDataSaverMode);
-            dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
-            dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
+            synchronized (mRulesLock) {
+                dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
+                dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
+            }
         }
 
-        synchronized (mUidFirewallRules) {
+        synchronized (mRulesLock) {
             dumpUidFirewallRule(pw, "", mUidFirewallRules);
-        }
 
-        pw.print("UID firewall standby chain enabled: "); pw.println(
-                mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY));
-        synchronized (mUidFirewallStandbyRules) {
+            pw.print("UID firewall standby chain enabled: "); pw.println(
+                    getFirewallChainState(FIREWALL_CHAIN_STANDBY));
             dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_STANDBY, mUidFirewallStandbyRules);
-        }
 
-        pw.print("UID firewall dozable chain enabled: "); pw.println(
-                mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE));
-        synchronized (mUidFirewallDozableRules) {
+            pw.print("UID firewall dozable chain enabled: "); pw.println(
+                    getFirewallChainState(FIREWALL_CHAIN_DOZABLE));
             dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_DOZABLE, mUidFirewallDozableRules);
-        }
 
-        pw.println("UID firewall powersave chain enabled: " +
-                mFirewallChainStates.get(FIREWALL_CHAIN_POWERSAVE));
-        synchronized (mUidFirewallPowerSaveRules) {
+            pw.println("UID firewall powersave chain enabled: " +
+                    getFirewallChainState(FIREWALL_CHAIN_POWERSAVE));
             dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_POWERSAVE, mUidFirewallPowerSaveRules);
         }
 
@@ -2576,4 +2616,99 @@
 
         return failures;
     }
+
+    private void setFirewallChainState(int chain, boolean state) {
+        synchronized (mRulesLock) {
+            mFirewallChainStates.put(chain, state);
+        }
+    }
+
+    private boolean getFirewallChainState(int chain) {
+        synchronized (mRulesLock) {
+            return mFirewallChainStates.get(chain);
+        }
+    }
+
+    @VisibleForTesting
+    class LocalService extends NetworkManagementInternal {
+        @Override
+        public boolean isNetworkRestrictedForUid(int uid) {
+            synchronized (mRulesLock) {
+                if (getFirewallChainState(FIREWALL_CHAIN_STANDBY)
+                        && mUidFirewallStandbyRules.get(uid) == FIREWALL_RULE_DENY) {
+                    if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of app standby mode");
+                    return true;
+                }
+                if (getFirewallChainState(FIREWALL_CHAIN_DOZABLE)
+                        && mUidFirewallDozableRules.get(uid) != FIREWALL_RULE_ALLOW) {
+                    if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of device idle mode");
+                    return true;
+                }
+                if (getFirewallChainState(FIREWALL_CHAIN_POWERSAVE)
+                        && mUidFirewallPowerSaveRules.get(uid) != FIREWALL_RULE_ALLOW) {
+                    if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of power saver mode");
+                    return true;
+                }
+                if (mUidRejectOnMetered.get(uid)) {
+                    if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
+                            + " in the background");
+                    return true;
+                }
+                if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) {
+                    if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
+                    return true;
+                }
+                return false;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    Injector getInjector() {
+        return new Injector();
+    }
+
+    @VisibleForTesting
+    class Injector {
+        void setDataSaverMode(boolean dataSaverMode) {
+            mDataSaverMode = dataSaverMode;
+        }
+
+        void setFirewallChainState(int chain, boolean state) {
+            NetworkManagementService.this.setFirewallChainState(chain, state);
+        }
+
+        void setFirewallRule(int chain, int uid, int rule) {
+            synchronized (mRulesLock) {
+                getUidFirewallRulesLR(chain).put(uid, rule);
+            }
+        }
+
+        void setUidOnMeteredNetworkList(boolean blacklist, int uid, boolean enable) {
+            synchronized (mRulesLock) {
+                if (blacklist) {
+                    mUidRejectOnMetered.put(uid, enable);
+                } else {
+                    mUidAllowOnMetered.put(uid, enable);
+                }
+            }
+        }
+
+        void reset() {
+            synchronized (mRulesLock) {
+                setDataSaverMode(false);
+                final int[] chains = {
+                        FIREWALL_CHAIN_DOZABLE,
+                        FIREWALL_CHAIN_STANDBY,
+                        FIREWALL_CHAIN_POWERSAVE
+                };
+                for (int chain : chains) {
+                    setFirewallChainState(chain, false);
+                    getUidFirewallRulesLR(chain).clear();
+                }
+                mUidAllowOnMetered.clear();
+                mUidRejectOnMetered.clear();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 891a13b..457c5f8 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -715,7 +715,8 @@
                         final Intent intent = new Intent(action,
                                 Uri.fromFile(userVol.getPathFile()));
                         intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol);
-                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
                         mContext.sendBroadcastAsUser(intent, userVol.getOwner());
                     }
                     break;
@@ -2079,6 +2080,20 @@
                 Binder.restoreCallingIdentity(token);
             }
         }
+
+        if ((mask & StorageManager.DEBUG_VIRTUAL_DISK) != 0) {
+            final boolean enabled = (flags & StorageManager.DEBUG_VIRTUAL_DISK) != 0;
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                SystemProperties.set(StorageManager.PROP_VIRTUAL_DISK, Boolean.toString(enabled));
+
+                // Reset storage to kick new setting into place
+                mHandler.obtainMessage(H_RESET).sendToTarget();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 5115fde..e4f4687 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -323,52 +323,6 @@
         }
 
         @Override
-        public void setTheme(String theme) {
-            if (getContext().checkCallingOrSelfPermission(
-                    android.Manifest.permission.MODIFY_THEME_OVERLAY)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Slog.e(TAG, "setTheme requires MODIFY_THEME_OVERLAY permission");
-                return;
-            }
-            SystemProperties.set("persist.vendor.overlay.theme", theme);
-            mHandler.post(() -> ShutdownThread.reboot(getContext(),
-                    PowerManager.SHUTDOWN_USER_REQUESTED, false));
-        }
-
-        @Override
-        public String getTheme() {
-            if (getContext().checkCallingOrSelfPermission(
-                    android.Manifest.permission.MODIFY_THEME_OVERLAY)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Slog.e(TAG, "setTheme requires MODIFY_THEME_OVERLAY permission");
-                return null;
-            }
-            return SystemProperties.get("persist.vendor.overlay.theme");
-        }
-
-        @Override
-        public String[] getAvailableThemes() {
-            if (getContext().checkCallingOrSelfPermission(
-                    android.Manifest.permission.MODIFY_THEME_OVERLAY)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Slog.e(TAG, "getAvailableThemes requires MODIFY_THEME_OVERLAY permission");
-                return null;
-            }
-            String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
-            if (TextUtils.isEmpty(def)) {
-                def = null;
-            }
-            String[] fileList = new File("/vendor/overlay").list();
-            if (fileList == null) return new String[0];
-            ArrayList<String> options = new ArrayList(fileList.length + 1);
-            Collections.addAll(options, fileList);
-            if (!options.contains(def)) {
-                options.add(0, def);
-            }
-            return options.toArray(new String[options.size()]);
-        }
-
-        @Override
         public int getNightMode() {
             synchronized (mLock) {
                 return mNightMode;
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index ce4ca02..80f89fc 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.hidl.manager.V1_0.IServiceManager;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IPowerManager;
@@ -42,6 +43,9 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
 
 /** This class calls its monitor every minute. Killing this process if they don't return **/
 public class Watchdog extends Thread {
@@ -75,6 +79,14 @@
         "com.android.bluetooth",  // Bluetooth service
     };
 
+    public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
+        "android.hardware.audio@2.0::IDevicesFactory",
+        "android.hardware.bluetooth@1.0::IBluetoothHci",
+        "android.hardware.camera.provider@2.4::ICameraProvider",
+        "android.hardware.vr@1.0::IVr",
+        "android.hardware.media.omx@1.0::IOmx"
+    );
+
     static Watchdog sWatchdog;
 
     /* This handler will be used to post message back onto the main thread */
@@ -344,6 +356,43 @@
         return builder.toString();
     }
 
+    private ArrayList<Integer> getInterestingHalPids() {
+        try {
+            IServiceManager serviceManager = IServiceManager.getService();
+            ArrayList<IServiceManager.InstanceDebugInfo> dump =
+                    serviceManager.debugDump();
+            HashSet<Integer> pids = new HashSet<>();
+            for (IServiceManager.InstanceDebugInfo info : dump) {
+                if (info.pid == IServiceManager.PidConstant.NO_PID) {
+                    continue;
+                }
+
+                if (!HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName)) {
+                    continue;
+                }
+
+                pids.add(info.pid);
+            }
+            return new ArrayList<Integer>(pids);
+        } catch (RemoteException e) {
+            return new ArrayList<Integer>();
+        }
+    }
+
+    private ArrayList<Integer> getInterestingNativePids() {
+        ArrayList<Integer> pids = getInterestingHalPids();
+
+        int[] nativePids = Process.getPidsForCommands(NATIVE_STACKS_OF_INTEREST);
+        if (nativePids != null) {
+            pids.ensureCapacity(pids.size() + nativePids.length);
+            for (int i : nativePids) {
+                pids.add(i);
+            }
+        }
+
+        return pids;
+    }
+
     @Override
     public void run() {
         boolean waitedHalf = false;
@@ -400,7 +449,7 @@
                         ArrayList<Integer> pids = new ArrayList<Integer>();
                         pids.add(Process.myPid());
                         ActivityManagerService.dumpStackTraces(true, pids, null, null,
-                                NATIVE_STACKS_OF_INTEREST);
+                            getInterestingNativePids());
                         waitedHalf = true;
                     }
                     continue;
@@ -417,13 +466,13 @@
             // Then kill this process so that the system will restart.
             EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
 
-            ArrayList<Integer> pids = new ArrayList<Integer>();
+            ArrayList<Integer> pids = new ArrayList<>();
             pids.add(Process.myPid());
             if (mPhonePid > 0) pids.add(mPhonePid);
             // Pass !waitedHalf so that just in case we somehow wind up here without having
             // dumped the halfway stacks, we properly re-initialize the trace file.
             final File stack = ActivityManagerService.dumpStackTraces(
-                    !waitedHalf, pids, null, null, NATIVE_STACKS_OF_INTEREST);
+                    !waitedHalf, pids, null, null, getInterestingNativePids());
 
             // Give some extra time to make sure the stack traces get written.
             // The system's been hanging for a minute, another second or two won't hurt much.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4fc60f9..272fbf8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -54,6 +54,7 @@
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
+import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
 import static android.provider.Settings.System.FONT_SCALE;
 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
@@ -357,6 +358,7 @@
 import com.android.server.IntentResolver;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
+import com.android.server.NetworkManagementInternal;
 import com.android.server.RescueParty;
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
@@ -572,9 +574,9 @@
     static final boolean TAKE_FULLSCREEN_SCREENSHOTS = true;
 
     /**
-     * Indicates the maximum time spent waiting for the network rules to get updated.
+     * Default value for {@link Settings.Global#NETWORK_ACCESS_TIMEOUT_MS}.
      */
-    private static final long WAIT_FOR_NETWORK_TIMEOUT_MS = 2000; // 2 sec
+    private static final long NETWORK_ACCESS_TIMEOUT_DEFAULT_MS = 0; // 0 sec
 
     /**
      * State indicating that there is no need for any blocking for network.
@@ -753,6 +755,12 @@
 
     final AppErrors mAppErrors;
 
+    /**
+     * Indicates the maximum time spent waiting for the network rules to get updated.
+     */
+    @VisibleForTesting
+    long mWaitForNetworkTimeoutMs;
+
     public boolean canShowErrorDialogs() {
         return mShowDialogs && !mSleeping && !mShuttingDown
                 && !mKeyguardController.isKeyguardShowing();
@@ -5460,11 +5468,12 @@
      *    appended to any existing file content.
      * @param firstPids of dalvik VM processes to dump stack traces for first
      * @param lastPids of dalvik VM processes to dump stack traces for last
-     * @param nativeProcs optional list of native process names to dump stack crawls
+     * @param nativePids optional list of native pids to dump stack crawls
      * @return file containing stack traces, or null if no dump file is configured
      */
     public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
+            ArrayList<Integer> nativePids) {
         String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
         if (tracesPath == null || tracesPath.length() == 0) {
             return null;
@@ -5480,7 +5489,7 @@
             return null;
         }
 
-        dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
+        dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativePids);
         return tracesFile;
     }
 
@@ -5522,7 +5531,8 @@
     }
 
     private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
+            ArrayList<Integer> nativePids) {
         // Use a FileObserver to detect when traces finish writing.
         // The order of traces is considered important to maintain for legibility.
         DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
@@ -5543,18 +5553,15 @@
             }
 
             // Next collect the stacks of the native pids
-            if (nativeProcs != null) {
-                int[] pids = Process.getPidsForCommands(nativeProcs);
-                if (pids != null) {
-                    for (int pid : pids) {
-                        if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
-                        final long sime = SystemClock.elapsedRealtime();
+            if (nativePids != null) {
+                for (int pid : nativePids) {
+                    if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
+                    final long sime = SystemClock.elapsedRealtime();
 
-                        Debug.dumpNativeBacktraceToFileTimeout(
-                                pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
-                        if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
-                                + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
-                    }
+                    Debug.dumpNativeBacktraceToFileTimeout(
+                            pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
+                    if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
+                            + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                 }
             }
 
@@ -13808,6 +13815,8 @@
         final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
         final boolean forceResizable = Settings.Global.getInt(
                 resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
+        final long waitForNetworkTimeoutMs = Settings.Global.getLong(resolver,
+                NETWORK_ACCESS_TIMEOUT_MS, NETWORK_ACCESS_TIMEOUT_DEFAULT_MS);
         final boolean supportsLeanbackOnly =
                 mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK_ONLY);
 
@@ -13863,6 +13872,7 @@
                 mFullscreenThumbnailScale = res.getFraction(
                     com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
             }
+            mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
         }
     }
 
@@ -22516,6 +22526,9 @@
     @VisibleForTesting
     @GuardedBy("this")
     void incrementProcStateSeqAndNotifyAppsLocked() {
+        if (mWaitForNetworkTimeoutMs <= 0) {
+            return;
+        }
         // Used for identifying which uids need to block for network.
         ArrayList<Integer> blockingUids = null;
         for (int i = mActiveUids.size() - 1; i >= 0; --i) {
@@ -23558,10 +23571,14 @@
                 }
                 final long startTime = SystemClock.uptimeMillis();
                 record.waitingForNetwork = true;
-                record.lock.wait(WAIT_FOR_NETWORK_TIMEOUT_MS);
+                record.lock.wait(mWaitForNetworkTimeoutMs);
                 record.waitingForNetwork = false;
                 final long totalTime = SystemClock.uptimeMillis() - startTime;
-                if (DEBUG_NETWORK ||  totalTime > WAIT_FOR_NETWORK_TIMEOUT_MS / 2) {
+                if (totalTime >= mWaitForNetworkTimeoutMs) {
+                    Slog.wtf(TAG_NETWORK, "Total time waited for network rules to get updated: "
+                            + totalTime + ". Uid: " + callingUid + " procStateSeq: "
+                            + procStateSeq);
+                } else if (DEBUG_NETWORK ||  totalTime >= mWaitForNetworkTimeoutMs / 2) {
                     Slog.d(TAG_NETWORK, "Total time waited for network rules to get updated: "
                             + totalTime + ". Uid: " + callingUid + " procStateSeq: "
                             + procStateSeq);
@@ -23856,6 +23873,8 @@
 
     @VisibleForTesting
     public static class Injector {
+        private NetworkManagementInternal mNmi;
+
         public AppOpsService getAppOpsService(File file, Handler handler) {
             return new AppOpsService(file, handler);
         }
@@ -23865,8 +23884,17 @@
         }
 
         public boolean isNetworkRestrictedForUid(int uid) {
-            // TODO: add implementation
+            if (ensureHasNetworkManagementInternal()) {
+                return mNmi.isNetworkRestrictedForUid(uid);
+            }
             return false;
         }
+
+        private boolean ensureHasNetworkManagementInternal() {
+            if (mNmi == null) {
+                mNmi = LocalServices.getService(NetworkManagementInternal.class);
+            }
+            return mNmi != null;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 04a09fe..5edfb06 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -10,8 +10,8 @@
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_ACTIVITY_NAME;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CALLING_PACKAGE_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CLASS_NAME;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_IS_EPHEMERAL;
@@ -313,7 +313,7 @@
             final LogMaker builder = new LogMaker(APP_TRANSITION);
             builder.setPackageName(info.launchedActivity.packageName);
             builder.setType(type);
-            builder.addTaggedData(APP_TRANSITION_ACTIVITY_NAME, info.launchedActivity.info.name);
+            builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name);
             if (info.launchedActivity.launchedFromPackage != null) {
                 builder.addTaggedData(APP_TRANSITION_CALLING_PACKAGE_NAME,
                         info.launchedActivity.launchedFromPackage);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 6cea483..9a1cd8c 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -47,6 +47,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.res.Configuration.EMPTY;
 import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
 import static android.os.Build.VERSION_CODES.HONEYCOMB;
 import static android.os.Build.VERSION_CODES.O;
@@ -83,6 +84,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Debug;
@@ -130,7 +132,7 @@
 /**
  * An entry in the history stack, representing an activity.
  */
-final class ActivityRecord implements AppWindowContainerListener {
+final class ActivityRecord extends ConfigurationContainer implements AppWindowContainerListener {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_AM;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     private static final String TAG_SAVED_STATE = TAG + POSTFIX_SAVED_STATE;
@@ -286,11 +288,19 @@
     // on the window.
     int mRotationAnimationHint = -1;
 
+    // The bounds of this activity. Mainly used for aspect-ratio compatibility.
+    // TODO(b/36505427): Every level on ConfigurationContainer now has bounds information, which
+    // directly affects the configuration. We should probably move this into that class and have it
+    // handle calculating override configuration from the bounds.
+    private final Rect mBounds = new Rect();
+
     /**
      * Temp configs used in {@link #ensureActivityConfigurationLocked(int, boolean)}
      */
     private final Configuration mTmpConfig1 = new Configuration();
     private final Configuration mTmpConfig2 = new Configuration();
+    private final Point mTmpPoint = new Point();
+    private final Rect mTmpBounds = new Rect();
 
     private static String startingWindowStateToString(int state) {
         switch (state) {
@@ -344,6 +354,13 @@
                 pw.println(mLastReportedConfiguration);
         pw.print(prefix); pw.print("mLastReportedOverrideConfiguration=");
                 pw.println(mLastReportedOverrideConfiguration);
+        pw.print(prefix); pw.print("CurrentConfiguration="); pw.println(getConfiguration());
+        if (!getOverrideConfiguration().equals(EMPTY)) {
+            pw.println(prefix + "OverrideConfiguration=" + getOverrideConfiguration());
+        }
+        if (!mBounds.isEmpty()) {
+            pw.println(prefix + "mBounds=" + mBounds);
+        }
         if (resultTo != null || resultWho != null) {
             pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
                     pw.print(" resultWho="); pw.print(resultWho);
@@ -461,10 +478,15 @@
         }
         if (info != null) {
             pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
-            pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
+            if (info.supportsPictureInPicture()) {
+                pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
+                pw.println(prefix + "supportsPictureInPictureWhilePausing: "
+                        + supportsPictureInPictureWhilePausing);
+            }
+            if (info.maxAspectRatio != 0) {
+                pw.println(prefix + "maxAspectRatio=" + info.maxAspectRatio);
+            }
         }
-        pw.println(prefix + "supportsPictureInPictureWhilePausing: "
-                + supportsPictureInPictureWhilePausing);
     }
 
     private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
@@ -579,6 +601,22 @@
         return task != null && task.getStackId() == FREEFORM_WORKSPACE_STACK_ID;
     }
 
+    @Override
+    protected int getChildCount() {
+        // {@link ActivityRecord} is a leaf node and has no children.
+        return 0;
+    }
+
+    @Override
+    protected ConfigurationContainer getChildAt(int index) {
+        return null;
+    }
+
+    @Override
+    protected ConfigurationContainer getParent() {
+        return task;
+    }
+
     static class Token extends IApplicationToken.Stub {
         private final WeakReference<ActivityRecord> weakActivity;
 
@@ -764,15 +802,20 @@
 
         inHistory = true;
 
-        task.updateOverrideConfigurationFromLaunchBounds();
         final TaskWindowContainerController taskController = task.getWindowContainerController();
 
+        // TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration()
+        task.updateOverrideConfigurationFromLaunchBounds();
+        // Make sure override configuration is up-to-date before using to create window controller.
+        updateOverrideConfiguration();
+
         mWindowContainerController = new AppWindowContainerController(taskController, appToken,
                 this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen,
                 (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
                 task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
                 appInfo.targetSdkVersion, mRotationAnimationHint,
-                ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);
+                ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L,
+                getOverrideConfiguration(), mBounds);
 
         task.addActivityToTop(this);
 
@@ -1994,8 +2037,73 @@
     }
 
     /** Call when override config was sent to the Window Manager to update internal records. */
+    // TODO(b/36505427): Why do we set last reported based on sending the config to WM? Seems like
+    // we should only set this when we actually report to the activity which is what the method
+    // setLastReportedMergedOverrideConfiguration() does. Investigate if this is really needed.
     void onOverrideConfigurationSent() {
-        mLastReportedOverrideConfiguration.setTo(task.getMergedOverrideConfiguration());
+        mLastReportedOverrideConfiguration.setTo(getMergedOverrideConfiguration());
+    }
+
+    @Override
+    void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        super.onOverrideConfigurationChanged(overrideConfiguration);
+        if (mWindowContainerController != null) {
+            mWindowContainerController.onOverrideConfigurationChanged(
+                    overrideConfiguration, mBounds);
+            // TODO(b/36505427): Can we consolidate the call points of onOverrideConfigurationSent()
+            // to just use this method instead?
+            onOverrideConfigurationSent();
+        }
+    }
+
+    // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+    private boolean updateOverrideConfiguration() {
+        computeBounds(mTmpBounds);
+        if (mTmpBounds.equals(mBounds)) {
+            return false;
+        }
+        mBounds.set(mTmpBounds);
+        // Bounds changed...update configuration to match.
+        mTmpConfig1.unset();
+        task.computeOverrideConfiguration(mTmpConfig1, mBounds, null /* insetBounds */,
+                false /* overrideWidth */, false /* overrideHeight */);
+        onOverrideConfigurationChanged(mTmpConfig1);
+        return true;
+    }
+
+    /** Computes the override configuration for this activity */
+    // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+    private void computeBounds(Rect outBounds) {
+        outBounds.setEmpty();
+        final float maxAspectRatio = info.maxAspectRatio;
+        final ActivityStack stack = getStack();
+        if ((task != null && !task.mFullscreen) || maxAspectRatio == 0 || stack == null) {
+            // We don't set override configuration if that activity task isn't fullscreen. I.e. the
+            // activity is in multi-window mode. Or, there isn't a max aspect ratio specified for
+            // the activity.
+            return;
+        }
+
+        stack.getDisplaySize(mTmpPoint);
+        int maxActivityWidth = mTmpPoint.x;
+        int maxActivityHeight = mTmpPoint.y;
+        if (mTmpPoint.x < mTmpPoint.y) {
+            // Width is the shorter side, so we use that to figure-out what the max. height should
+            // be given the aspect ratio.
+            maxActivityHeight = (int) ((maxActivityWidth * maxAspectRatio) + 0.5f);
+        } else {
+            // Height is the shorter side, so we use that to figure-out what the max. width should
+            // be given the aspect ratio.
+            maxActivityWidth = (int) ((maxActivityHeight * maxAspectRatio) + 0.5f);
+        }
+
+        if (mTmpPoint.x <= maxActivityWidth && mTmpPoint.y <= maxActivityHeight) {
+            // The display matches or is less than the activity aspect ratio, so nothing else to do.
+            return;
+        }
+
+        // Compute configuration based on max supported width and height.
+        outBounds.set(0, 0, maxActivityWidth, maxActivityHeight);
     }
 
     /**
@@ -2028,13 +2136,16 @@
         if (displayChanged) {
             mLastReportedDisplayId = newDisplayId;
         }
+        // TODO(b/36505427): Is there a better place to do this?
+        updateOverrideConfiguration();
+
         // Short circuit: if the two full configurations are equal (the common case), then there is
         // nothing to do.  We test the full configuration instead of the global and merged override
         // configurations because there are cases (like moving a task to the pinned stack) where
         // the combine configurations are equal, but would otherwise differ in the override config
         mTmpConfig1.setTo(mLastReportedConfiguration);
         mTmpConfig1.updateFrom(mLastReportedOverrideConfiguration);
-        if (task.getConfiguration().equals(mTmpConfig1) && !forceNewConfig && !displayChanged) {
+        if (getConfiguration().equals(mTmpConfig1) && !forceNewConfig && !displayChanged) {
             if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                     "Configuration & display unchanged in " + this);
             return true;
@@ -2045,15 +2156,15 @@
 
         // Find changes between last reported merged configuration and the current one. This is used
         // to decide whether to relaunch an activity or just report a configuration change.
-        final int changes = getTaskConfigurationChanges(mTmpConfig1);
+        final int changes = getConfigurationChanges(mTmpConfig1);
 
         // Update last reported values.
         final Configuration newGlobalConfig = service.getGlobalConfiguration();
-        final Configuration newTaskMergedOverrideConfig = task.getMergedOverrideConfiguration();
+        final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();
         mTmpConfig1.setTo(mLastReportedConfiguration);
         mTmpConfig2.setTo(mLastReportedOverrideConfiguration);
         mLastReportedConfiguration.setTo(newGlobalConfig);
-        mLastReportedOverrideConfiguration.setTo(newTaskMergedOverrideConfig);
+        mLastReportedOverrideConfiguration.setTo(newMergedOverrideConfig);
 
         if (changes == 0 && !forceNewConfig) {
             if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
@@ -2061,9 +2172,9 @@
             // There are no significant differences, so we won't relaunch but should still deliver
             // the new configuration to the client process.
             if (displayChanged) {
-                scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+                scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
             } else {
-                scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+                scheduleConfigurationChanged(newMergedOverrideConfig);
             }
             return true;
         }
@@ -2088,9 +2199,9 @@
                         + Integer.toHexString(changes) + ", handles=0x"
                         + Integer.toHexString(info.getRealConfigChanged())
                         + ", newGlobalConfig=" + newGlobalConfig
-                        + ", newTaskMergedOverrideConfig=" + newTaskMergedOverrideConfig);
+                        + ", newMergedOverrideConfig=" + newMergedOverrideConfig);
 
-        if (shouldRelaunchLocked(changes, newGlobalConfig, newTaskMergedOverrideConfig)
+        if (shouldRelaunchLocked(changes, newGlobalConfig, newMergedOverrideConfig)
                 || forceNewConfig) {
             // Aha, the activity isn't handling the change, so DIE DIE DIE.
             configChangeFlags |= changes;
@@ -2133,13 +2244,13 @@
         }
 
         // Default case: the activity can handle this new configuration, so hand it over.
-        // NOTE: We only forward the task override configuration as the system level configuration
+        // NOTE: We only forward the override configuration as the system level configuration
         // changes is always sent to all processes when they happen so it can just use whatever
         // system level configuration it last got.
         if (displayChanged) {
-            scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+            scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
         } else {
-            scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+            scheduleConfigurationChanged(newMergedOverrideConfig);
         }
         stopFreezingScreenLocked(false);
 
@@ -2167,31 +2278,31 @@
         return (changes&(~configChanged)) != 0;
     }
 
-    private int getTaskConfigurationChanges(Configuration lastReportedConfig) {
+    private int getConfigurationChanges(Configuration lastReportedConfig) {
         // Determine what has changed.  May be nothing, if this is a config that has come back from
         // the app after going idle.  In that case we just want to leave the official config object
         // now in the activity and do nothing else.
-        final Configuration currentConfig = task.getConfiguration();
-        int taskChanges = lastReportedConfig.diff(currentConfig);
+        final Configuration currentConfig = getConfiguration();
+        int changes = lastReportedConfig.diff(currentConfig);
         // We don't want to use size changes if they don't cross boundaries that are important to
         // the app.
-        if ((taskChanges & CONFIG_SCREEN_SIZE) != 0) {
+        if ((changes & CONFIG_SCREEN_SIZE) != 0) {
             final boolean crosses = crossesHorizontalSizeThreshold(lastReportedConfig.screenWidthDp,
                     currentConfig.screenWidthDp)
                     || crossesVerticalSizeThreshold(lastReportedConfig.screenHeightDp,
                     currentConfig.screenHeightDp);
             if (!crosses) {
-                taskChanges &= ~CONFIG_SCREEN_SIZE;
+                changes &= ~CONFIG_SCREEN_SIZE;
             }
         }
-        if ((taskChanges & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
+        if ((changes & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
             final int oldSmallest = lastReportedConfig.smallestScreenWidthDp;
             final int newSmallest = currentConfig.smallestScreenWidthDp;
             if (!crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
-                taskChanges &= ~CONFIG_SMALLEST_SCREEN_SIZE;
+                changes &= ~CONFIG_SMALLEST_SCREEN_SIZE;
             }
         }
-        return taskChanges;
+        return changes;
     }
 
     private static boolean isResizeOnlyChange(int change) {
@@ -2232,7 +2343,7 @@
             app.thread.scheduleRelaunchActivity(appToken, pendingResults, pendingNewIntents,
                     configChangeFlags, !andResume,
                     new Configuration(service.getGlobalConfiguration()),
-                    new Configuration(task.getMergedOverrideConfiguration()), preserveWindow);
+                    new Configuration(getMergedOverrideConfiguration()), preserveWindow);
             // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
             // pass in 'andResume' if this activity is currently resumed, which implies we aren't
             // sleeping.
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9a4f804..28a4e1a 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -759,6 +759,12 @@
         }
     }
 
+    final void removeActivitiesFromLRUListLocked(TaskRecord task) {
+        for (ActivityRecord r : task.mActivities) {
+            mLRUActivities.remove(r);
+        }
+    }
+
     final boolean updateLRUListLocked(ActivityRecord r) {
         final boolean hadit = mLRUActivities.remove(r);
         mLRUActivities.add(r);
@@ -4947,6 +4953,7 @@
             }
         }
         mTaskHistory.remove(task);
+        removeActivitiesFromLRUListLocked(task);
         updateTaskMovement(task, true);
 
         if (mode == REMOVE_TASK_MODE_DESTROYING && task.mActivities.isEmpty()) {
@@ -5114,6 +5121,7 @@
         // Apps may depend on onResume()/onPause() being called in pairs.
         if (setResume) {
             mResumedActivity = r;
+            updateLRUListLocked(r);
         }
         // If the activity was previously pausing, then ensure we transfer that as well
         if (setPause) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 97d0aa3..8559dca 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1380,7 +1380,7 @@
                 new Configuration(mService.getGlobalConfiguration());
             r.setLastReportedGlobalConfiguration(globalConfiguration);
             final Configuration mergedOverrideConfiguration =
-                new Configuration(task.getMergedOverrideConfiguration());
+                new Configuration(r.getMergedOverrideConfiguration());
             r.setLastReportedMergedOverrideConfiguration(mergedOverrideConfiguration);
 
             app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
@@ -3555,8 +3555,16 @@
                 stackHeader.append("  mFullscreen=" + stack.mFullscreen);
                 stackHeader.append("\n");
                 stackHeader.append("  mBounds=" + stack.mBounds);
-                printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
-                        needSep, stackHeader.toString());
+
+                final boolean printedStackHeader = stack.dumpActivitiesLocked(fd, pw, dumpAll,
+                        dumpClient, dumpPackage, needSep, stackHeader.toString());
+                printed |= printedStackHeader;
+                if (!printedStackHeader) {
+                    // Ensure we always dump the stack header even if there are no activities
+                    pw.println();
+                    pw.println(stackHeader);
+                }
+
                 printed |= dumpHistoryList(fd, pw, stack.mLRUActivities, "    ", "Run", false,
                         !dumpAll, false, dumpPackage, true,
                         "    Running activities (most recent first):", null);
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 1b7b225..4bd06b7 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1790,21 +1790,7 @@
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
 
-        if (mLaunchBounds != null) {
-            mInTask.updateOverrideConfiguration(mLaunchBounds);
-            int stackId = mInTask.getLaunchStackId();
-            if (stackId != mInTask.getStackId()) {
-                mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
-                        DEFER_RESUME, "inTaskToFront");
-                stackId = mInTask.getStackId();
-            }
-            if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
-            }
-        }
         mTargetStack = mInTask.getStack();
-        mTargetStack.moveTaskToFrontLocked(
-                mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
 
         // Check whether we should actually launch the new activity in to the task,
         // or just reuse the current activity on top.
@@ -1813,6 +1799,8 @@
                 && top.userId == mStartActivity.userId) {
             if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                     || mLaunchSingleTop || mLaunchSingleTask) {
+                mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+                        mStartActivity.appTimeTracker, "inTaskToFront");
                 ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.task);
                 if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
                     // We don't need to start a new activity, and the client said not to do
@@ -1826,12 +1814,31 @@
         }
 
         if (!mAddingToTask) {
+            mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+                    mStartActivity.appTimeTracker, "inTaskToFront");
             // We don't actually want to have this activity added to the task, so just
             // stop here but still tell the caller that we consumed the intent.
             ActivityOptions.abort(mOptions);
             return START_TASK_TO_FRONT;
         }
 
+        if (mLaunchBounds != null) {
+            mInTask.updateOverrideConfiguration(mLaunchBounds);
+            int stackId = mInTask.getLaunchStackId();
+            if (stackId != mInTask.getStackId()) {
+                mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+                        DEFER_RESUME, "inTaskToFront");
+                stackId = mInTask.getStackId();
+                mTargetStack = mInTask.getStack();
+            }
+            if (StackId.resizeStackWithLaunchBounds(stackId)) {
+                mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+            }
+        }
+
+        mTargetStack.moveTaskToFrontLocked(
+                mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
+
         addOrReparentStartingActivity(mInTask, "setTaskFromInTask");
         if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
                 + " in explicit task " + mStartActivity.task);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index f927cce..7b1af38 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -870,12 +870,22 @@
             nativeProcs = NATIVE_STACKS_OF_INTEREST;
         }
 
+        int[] pids = Process.getPidsForCommands(nativeProcs);
+        ArrayList<Integer> nativePids = null;
+
+        if (pids != null) {
+            nativePids = new ArrayList<Integer>(pids.length);
+            for (int i : pids) {
+                nativePids.add(i);
+            }
+        }
+
         // For background ANRs, don't pass the ProcessCpuTracker to
         // avoid spending 1/2 second collecting stats to rank lastPids.
         File tracesFile = mService.dumpStackTraces(true, firstPids,
                                                    (isSilentANR) ? null : processCpuTracker,
                                                    (isSilentANR) ? null : lastPids,
-                                                   nativeProcs);
+                                                   nativePids);
 
         String cpuInfo = null;
         if (ActivityManagerService.MONITOR_CPU_USAGE) {
diff --git a/services/core/java/com/android/server/am/ConfigurationContainer.java b/services/core/java/com/android/server/am/ConfigurationContainer.java
index a3e95b8..3d60681 100644
--- a/services/core/java/com/android/server/am/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/am/ConfigurationContainer.java
@@ -22,6 +22,8 @@
  * Contains common logic for classes that have override configurations and are organized in a
  * hierarchy.
  */
+// TODO(b/36505427): Move to wm package and have WindowContainer use this instead of having its own
+// implementation for merging configuration.
 abstract class ConfigurationContainer<E extends ConfigurationContainer> {
 
     /** Contains override configuration settings applied to this configuration container. */
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a668fea..fd65c10 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -924,12 +924,12 @@
 
     @Override
     protected int getChildCount() {
-        return 0;
+        return mActivities.size();
     }
 
     @Override
     protected ConfigurationContainer getChildAt(int index) {
-        return null;
+        return mActivities.get(index);
     }
 
     @Override
@@ -944,7 +944,7 @@
     }
 
     // Close up recents linked list.
-    void closeRecentsChain() {
+    private void closeRecentsChain() {
         if (mPrevAffiliate != null) {
             mPrevAffiliate.setNextAffiliate(mNextAffiliate);
         }
@@ -1188,7 +1188,10 @@
             throw new IllegalArgumentException("Can not add r=" + " to task=" + this
                     + " current parent=" + r.task);
         }
+        // TODO(b/36505427): Maybe make task private to ActivityRecord so we can also do
+        // onParentChanged() within the setter?
         r.task = this;
+        r.onParentChanged();
 
         // Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
         if (!mActivities.remove(r) && r.fullscreen) {
@@ -1995,7 +1998,7 @@
             if (mStack == null || StackId.persistTaskBounds(mStack.mStackId)) {
                 mLastNonFullscreenBounds = mBounds;
             }
-            calculateOverrideConfig(newConfig, mTmpRect, insetBounds,
+            computeOverrideConfiguration(newConfig, mTmpRect, insetBounds,
                     mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
         }
         onOverrideConfigurationChanged(newConfig);
@@ -2008,7 +2011,10 @@
     }
 
     /** Clears passed config and fills it with new override values. */
-    private void calculateOverrideConfig(Configuration config, Rect bounds, Rect insetBounds,
+    // TODO(b/36505427): TaskRecord.computeOverrideConfiguration() is a utility method that doesn't
+    // depend on task or stacks, but uses those object to get the display to base the calculation
+    // on. Probably best to centralize calculations like this in ConfigurationContainer.
+    void computeOverrideConfiguration(Configuration config, Rect bounds, Rect insetBounds,
             boolean overrideWidth, boolean overrideHeight) {
         mTmpNonDecorBounds.set(bounds);
         mTmpStableBounds.set(bounds);
@@ -2027,7 +2033,7 @@
             config.smallestScreenWidthDp =
                     mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
             config.screenWidthDp = config.screenHeightDp = config.smallestScreenWidthDp;
-            Slog.wtf(TAG, "Expected stack when caclulating override config");
+            Slog.wtf(TAG, "Expected stack when calculating override config");
         }
 
         config.orientation = (config.screenWidthDp <= config.screenHeightDp)
@@ -2048,23 +2054,6 @@
 
     }
 
-    /**
-     * Using the existing configuration {@param config}, creates a new task override config such
-     * that all the fields that are usually set in an override config are set to the ones in
-     * {@param config}.
-     */
-    Configuration extractOverrideConfig(Configuration config) {
-        final Configuration extracted = new Configuration();
-        extracted.screenWidthDp = config.screenWidthDp;
-        extracted.screenHeightDp = config.screenHeightDp;
-        extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
-        extracted.orientation = config.orientation;
-        // We're only overriding LONG, SIZE and COMPAT parts of screenLayout.
-        extracted.screenLayout = config.screenLayout & (Configuration.SCREENLAYOUT_LONG_MASK
-                | Configuration.SCREENLAYOUT_SIZE_MASK | Configuration.SCREENLAYOUT_COMPAT_NEEDED);
-        return extracted;
-    }
-
     Rect updateOverrideConfigurationFromLaunchBounds() {
         final Rect bounds = validateBounds(getLaunchBounds());
         updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f5ea74d..8cc9375 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -842,7 +842,7 @@
 
     /** Component used to install ephemeral applications */
     ComponentName mInstantAppInstallerComponent;
-    final ActivityInfo mInstantAppInstallerActivity = new ActivityInfo();
+    ActivityInfo mInstantAppInstallerActivity;
     final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo();
 
     final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
@@ -2931,13 +2931,16 @@
 
     private void updateInstantAppInstallerLocked() {
         final ComponentName oldInstantAppInstallerComponent = mInstantAppInstallerComponent;
-        final ComponentName newInstantAppInstallerComponent = getEphemeralInstallerLPr();
+        final ActivityInfo newInstantAppInstaller = getEphemeralInstallerLPr();
+        ComponentName newInstantAppInstallerComponent = newInstantAppInstaller == null
+                ? null : newInstantAppInstaller.getComponentName();
+
         if (newInstantAppInstallerComponent != null
                 && !newInstantAppInstallerComponent.equals(oldInstantAppInstallerComponent)) {
             if (DEBUG_EPHEMERAL) {
                 Slog.d(TAG, "Set ephemeral installer: " + newInstantAppInstallerComponent);
             }
-            setUpInstantAppInstallerActivityLP(newInstantAppInstallerComponent);
+            setUpInstantAppInstallerActivityLP(newInstantAppInstaller);
         } else if (DEBUG_EPHEMERAL && newInstantAppInstallerComponent == null) {
             Slog.d(TAG, "Unset ephemeral installer; none available");
         }
@@ -3160,7 +3163,7 @@
         return null;
     }
 
-    private @Nullable ComponentName getEphemeralInstallerLPr() {
+    private @Nullable ActivityInfo getEphemeralInstallerLPr() {
         final Intent intent = new Intent(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
         intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
@@ -3186,7 +3189,7 @@
         if (matches.size() == 0) {
             return null;
         } else if (matches.size() == 1) {
-            return matches.get(0).getComponentInfo().getComponentName();
+            return (ActivityInfo) matches.get(0).getComponentInfo();
         } else {
             throw new RuntimeException(
                     "There must be at most one ephemeral installer; found " + matches);
@@ -10642,28 +10645,23 @@
         }
     }
 
-    private void setUpInstantAppInstallerActivityLP(ComponentName installerComponent) {
-        if (installerComponent == null) {
+    private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) {
+        if (installerActivity == null) {
             if (DEBUG_EPHEMERAL) {
                 Slog.d(TAG, "Clear ephemeral installer activity");
             }
-            mInstantAppInstallerActivity.applicationInfo = null;
+            mInstantAppInstallerActivity = null;
             return;
         }
 
         if (DEBUG_EPHEMERAL) {
-            Slog.d(TAG, "Set ephemeral installer activity: " + installerComponent);
+            Slog.d(TAG, "Set ephemeral installer activity: "
+                    + installerActivity.getComponentName());
         }
-        final PackageParser.Package pkg = mPackages.get(installerComponent.getPackageName());
         // Set up information for ephemeral installer activity
-        mInstantAppInstallerActivity.applicationInfo = pkg.applicationInfo;
-        mInstantAppInstallerActivity.name = installerComponent.getClassName();
-        mInstantAppInstallerActivity.packageName = pkg.applicationInfo.packageName;
-        mInstantAppInstallerActivity.processName = pkg.applicationInfo.packageName;
-        mInstantAppInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
-        mInstantAppInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
+        mInstantAppInstallerActivity = installerActivity;
+        mInstantAppInstallerActivity.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
                 | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
-        mInstantAppInstallerActivity.theme = 0;
         mInstantAppInstallerActivity.exported = true;
         mInstantAppInstallerActivity.enabled = true;
         mInstantAppInstallerInfo.activityInfo = mInstantAppInstallerActivity;
diff --git a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
index cd55f50..7d53310 100644
--- a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
+++ b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
@@ -27,6 +27,7 @@
 import android.media.AudioAttributes;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.Vibrator;
@@ -58,6 +59,9 @@
     private final Context mContext;
     private AlertDialog mAlertDialog;
     private boolean mIsShortcutEnabled;
+    private boolean mEnabledOnLockScreen;
+    private int mUserId;
+
     // Visible for testing
     public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
 
@@ -72,29 +76,55 @@
         return context.getString(R.string.config_defaultAccessibilityService);
     }
 
-    public AccessibilityShortcutController(Context context, Handler handler) {
+    public AccessibilityShortcutController(Context context, Handler handler, int initialUserId) {
         mContext = context;
 
-        // Keep track of state of shortcut
+        // Keep track of state of shortcut settings
+        final ContentObserver co = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri, int userId) {
+                if (userId == mUserId) {
+                    onSettingsChanged();
+                }
+            }
+        };
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
-                false,
-                new ContentObserver(handler) {
-                    @Override
-                    public void onChange(boolean selfChange) {
-                        onSettingsChanged();
-                    }
-                },
-                UserHandle.USER_ALL);
-        updateShortcutEnabled();
+                false, co, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED),
+                false, co, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN),
+                false, co, UserHandle.USER_ALL);
+        setCurrentUser(mUserId);
     }
 
-    public boolean isAccessibilityShortcutAvailable() {
-        return mIsShortcutEnabled;
+    public void setCurrentUser(int currentUserId) {
+        mUserId = currentUserId;
+        onSettingsChanged();
+    }
+
+    /**
+     * Check if the shortcut is available.
+     *
+     * @param onLockScreen Whether or not the phone is currently locked.
+     *
+     * @return {@code true} if the shortcut is available
+     */
+    public boolean isAccessibilityShortcutAvailable(boolean phoneLocked) {
+        return mIsShortcutEnabled && (!phoneLocked || mEnabledOnLockScreen);
     }
 
     public void onSettingsChanged() {
-        updateShortcutEnabled();
+        final boolean haveValidService =
+                !TextUtils.isEmpty(getTargetServiceComponentNameString(mContext, mUserId));
+        final ContentResolver cr = mContext.getContentResolver();
+        final boolean enabled = Settings.Secure.getIntForUser(
+                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, mUserId) == 1;
+        mEnabledOnLockScreen = Settings.Secure.getIntForUser(
+                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, 0, mUserId) == 1;
+        mIsShortcutEnabled = enabled && haveValidService;
     }
 
     /**
@@ -171,11 +201,6 @@
         }
     }
 
-    private void updateShortcutEnabled() {
-        mIsShortcutEnabled = !TextUtils.isEmpty(getTargetServiceComponentNameString(
-                mContext, UserHandle.myUserId()));
-    }
-
     private AlertDialog createShortcutWarningDialog(int userId) {
         final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 31e22b9..52f6955 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1547,7 +1547,7 @@
     }
 
     private void interceptAccessibilityShortcutChord() {
-        if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable()
+        if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(isKeyguardLocked())
                 && mScreenshotChordVolumeDownKeyTriggered && mA11yShortcutChordVolumeUpKeyTriggered
                 && !mScreenshotChordPowerKeyTriggered) {
             final long now = SystemClock.uptimeMillis();
@@ -1771,7 +1771,7 @@
         mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
         mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
         mAccessibilityShortcutController =
-                new AccessibilityShortcutController(mContext, new Handler());
+                new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
         // Init display burn-in protection
         boolean burnInProtectionEnabled = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_enableBurnInProtection);
@@ -3243,7 +3243,7 @@
 
         // If an accessibility shortcut might be partially complete, hold off dispatching until we
         // know if it is complete or not
-        if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable()
+        if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(false)
                 && (flags & KeyEvent.FLAG_FALLBACK) == 0) {
             if (mScreenshotChordVolumeDownKeyTriggered ^ mA11yShortcutChordVolumeUpKeyTriggered) {
                 final long now = SystemClock.uptimeMillis();
@@ -5823,9 +5823,7 @@
                             mScreenshotChordVolumeDownKeyConsumed = false;
                             cancelPendingPowerKeyAction();
                             interceptScreenshotChord();
-                            if (!isKeyguardLocked()) {
-                                interceptAccessibilityShortcutChord();
-                            }
+                            interceptAccessibilityShortcutChord();
                         }
                     } else {
                         mScreenshotChordVolumeDownKeyTriggered = false;
@@ -5841,9 +5839,7 @@
                             mA11yShortcutChordVolumeUpKeyConsumed = false;
                             cancelPendingPowerKeyAction();
                             cancelPendingScreenshotChordAction();
-                            if (!isKeyguardLocked()) {
-                                interceptAccessibilityShortcutChord();
-                            }
+                            interceptAccessibilityShortcutChord();
                         }
                     } else {
                         mA11yShortcutChordVolumeUpKeyTriggered = false;
@@ -7945,6 +7941,9 @@
         if (mKeyguardDelegate != null) {
             mKeyguardDelegate.setCurrentUser(newUserId);
         }
+        if (mAccessibilityShortcutController != null) {
+            mAccessibilityShortcutController.setCurrentUser(newUserId);
+        }
         StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
         if (statusBar != null) {
             statusBar.setCurrentUser(newUserId);
diff --git a/services/core/java/com/android/server/vr/CompatibilityDisplay.java b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
index 8f95cc7..a8d6223 100644
--- a/services/core/java/com/android/server/vr/CompatibilityDisplay.java
+++ b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
@@ -1,4 +1,3 @@
-
 package com.android.server.vr;
 
 import static android.view.Display.INVALID_DISPLAY;
@@ -8,14 +7,18 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.ImageFormat;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.service.vr.IVrStateCallbacks;
+import android.os.SystemProperties;
+import android.service.vr.IPersistentVrStateCallbacks;
 import android.service.vr.IVrManager;
 import android.util.Log;
 import android.view.Surface;
@@ -34,11 +37,12 @@
     private final static int HEIGHT = 1800;
     private final static int WIDTH = 1400;
     private final static int DPI = 320;
+    private final static int STOP_VIRTUAL_DISPLAY_DELAY_MILLIS = 2000;
 
     private final static String DEBUG_ACTION_SET_MODE =
             "com.android.server.vr.CompatibilityDisplay.SET_MODE";
     private final static String DEBUG_EXTRA_MODE_ON =
-            "com.android.servier.vr.CompatibilityDisplay.EXTRA_MODE_ON";
+            "com.android.server.vr.CompatibilityDisplay.EXTRA_MODE_ON";
     private final static String DEBUG_ACTION_SET_SURFACE =
             "com.android.server.vr.CompatibilityDisplay.SET_SURFACE";
     private final static String DEBUG_EXTRA_SURFACE =
@@ -46,14 +50,16 @@
 
     private final DisplayManager mDisplayManager;
     private final IVrManager mVrManager;
+    private final Object mVdLock = new Object();
+    private final Handler mHandler = new Handler();
 
-    // TODO: Lock initially created when VrStateCallback was connected through Binder. This may not
-    // be necessary with the direct access to VrManager.
-    private final Object vdLock = new Object();
-
-    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+    /**
+     * Callback implementation to receive changes to VrMode.
+     **/
+    private final IPersistentVrStateCallbacks mVrStateCallbacks =
+            new IPersistentVrStateCallbacks.Stub() {
         @Override
-        public void onVrStateChanged(boolean enabled) {
+        public void onPersistentVrStateChanged(boolean enabled) {
             if (enabled != mIsVrModeEnabled) {
                 mIsVrModeEnabled = enabled;
                 updateVirtualDisplay();
@@ -63,7 +69,9 @@
 
     private VirtualDisplay mVirtualDisplay;
     private Surface mSurface;
-    private boolean mIsDebugOverrideEnabled;
+    private ImageReader mImageReader;
+    private Runnable mStopVDRunnable;
+    private boolean mIsVrModeOverrideEnabled;
     private boolean mIsVrModeEnabled;
 
     public CompatibilityDisplay(DisplayManager displayManager, IVrManager vrManager) {
@@ -79,13 +87,23 @@
         startDebugOnlyBroadcastReceiver(context);
     }
 
+    /**
+     * Creates and Destroys the virtual display depending on the current state of VrMode.
+     */
     private void updateVirtualDisplay() {
-        if (mIsVrModeEnabled || (DEBUG && mIsDebugOverrideEnabled)) {
+        boolean createVirtualDisplay = "true".equals(SystemProperties.get("vr_virtualdisplay"));
+        if (DEBUG) {
+            Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", createVD: " + createVirtualDisplay +
+                    ", override: " + mIsVrModeOverrideEnabled);
+        }
+
+        if (mIsVrModeEnabled || (createVirtualDisplay && mIsVrModeOverrideEnabled)) {
             // TODO: Consider not creating the display until ActivityManager needs one on
             // which to display a 2D application.
-            // TODO: STOPSHIP Remove DEBUG conditional before launching.
-            if (DEBUG) {
+            // TODO: STOPSHIP Remove createVirtualDisplay conditional before launching.
+            if (createVirtualDisplay) {
                 startVirtualDisplay();
+                startImageReader();
             }
         } else {
             // Stop virtual display to test exit condition
@@ -93,8 +111,17 @@
         }
     }
 
+    /**
+     * Creates a DEBUG-only BroadcastReceiver through which a test app can simulate VrMode and
+     * set a custom Surface for the virtual display.  This allows testing of the virtual display
+     * without going into full 3D.
+     *
+     * @param context The context.
+     */
     private void startDebugOnlyBroadcastReceiver(Context context) {
-        if (DEBUG) {
+        // STOPSHIP: remove vr_debug_vd_receiver test.
+        boolean debugBroadcast = "true".equals(SystemProperties.get("vr_debug_vd_receiver"));
+        if (DEBUG || debugBroadcast) {
             IntentFilter intentFilter = new IntentFilter(DEBUG_ACTION_SET_MODE);
             intentFilter.addAction(DEBUG_ACTION_SET_SURFACE);
 
@@ -103,21 +130,13 @@
                 public void onReceive(Context context, Intent intent) {
                     final String action = intent.getAction();
                     if (DEBUG_ACTION_SET_MODE.equals(action)) {
-                        mIsDebugOverrideEnabled =
+                        mIsVrModeOverrideEnabled =
                                 intent.getBooleanExtra(DEBUG_EXTRA_MODE_ON, false);
                         updateVirtualDisplay();
                     } else if (DEBUG_ACTION_SET_SURFACE.equals(action)) {
                         if (mVirtualDisplay != null) {
-                            final Surface newSurface =
-                                    intent.getParcelableExtra(DEBUG_EXTRA_SURFACE);
-
-                            Log.i(TAG, "Setting the new surface from " + mSurface + " to " + newSurface);
-                            if (newSurface != mSurface) {
-                                mVirtualDisplay.setSurface(newSurface);
-                                if (mSurface != null) {
-                                    mSurface.release();
-                                }
-                                mSurface = newSurface;
+                            if (intent.hasExtra(DEBUG_EXTRA_SURFACE)) {
+                                setSurfaceLocked(intent.getParcelableExtra(DEBUG_EXTRA_SURFACE));
                             }
                         } else {
                             Log.w(TAG, "Cannot set the surface because the VD is null.");
@@ -128,18 +147,27 @@
         }
     }
 
+    /**
+     * Starts listening to VrMode changes.
+     */
     private void startVrModeListener() {
         if (mVrManager != null) {
             try {
-                mVrManager.registerListener(mVrStateCallbacks);
+                mVrManager.registerPersistentVrStateListener(mVrStateCallbacks);
             } catch (RemoteException e) {
                 Log.e(TAG, "Could not register VR State listener.", e);
             }
         }
     }
 
+    /**
+     * Returns the virtual display ID if one currently exists, otherwise returns
+     * {@link INVALID_DISPLAY_ID}.
+     *
+     * @return The virtual display ID.
+     */
     public int getVirtualDisplayId() {
-        synchronized(vdLock) {
+        synchronized(mVdLock) {
             if (mVirtualDisplay != null) {
                 int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId();
                 if (DEBUG) {
@@ -151,6 +179,9 @@
         return INVALID_DISPLAY;
     }
 
+    /**
+     * Starts the virtual display if one does not already exist.
+     */
     private void startVirtualDisplay() {
         if (DEBUG) {
             Log.d(TAG, "Request to start VD, DM:" + mDisplayManager);
@@ -161,7 +192,7 @@
             return;
         }
 
-        synchronized (vdLock) {
+        synchronized (mVdLock) {
             if (mVirtualDisplay != null) {
                 Log.i(TAG, "VD already exists, ignoring request");
                 return;
@@ -169,10 +200,6 @@
 
             mVirtualDisplay = mDisplayManager.createVirtualDisplay("VR 2D Display", WIDTH, HEIGHT,
                     DPI, null /* Surface */, 0 /* flags */);
-            if (mVirtualDisplay != null && mSurface != null && mSurface.isValid()) {
-              // TODO: Need to protect all setSurface calls with a lock.
-              mVirtualDisplay.setSurface(mSurface);
-            }
         }
 
         if (DEBUG) {
@@ -180,16 +207,69 @@
         }
     }
 
+    /**
+     * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout.
+     * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out
+     * of being enabled. This can happen sometimes with our 2D test app.
+     */
     private void stopVirtualDisplay() {
-        if (DEBUG) {
-            Log.i(TAG, "Santos, stopping VD");
+        if (mStopVDRunnable == null) {
+           mStopVDRunnable = new Runnable() {
+               @Override
+               public void run() {
+                    if (mIsVrModeEnabled) {
+                        Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on.");
+                    } else {
+                        Log.i(TAG, "Stopping Virtual Display");
+                        synchronized (mVdLock) {
+                            setSurfaceLocked(null); // clean up and release the surface first.
+                            if (mVirtualDisplay != null) {
+                                mVirtualDisplay.release();
+                                mVirtualDisplay = null;
+                            }
+                        }
+                    }
+               }
+           };
         }
 
-        synchronized (vdLock) {
+        mHandler.removeCallbacks(mStopVDRunnable);
+        mHandler.postDelayed(mStopVDRunnable, STOP_VIRTUAL_DISPLAY_DELAY_MILLIS);
+    }
+
+    /**
+     * Set the surface to use with the virtual display.
+     *
+     * Code should be locked by {@link #mVdLock} before invoked.
+     *
+     * @param surface The Surface to set.
+     */
+    private void setSurfaceLocked(Surface surface) {
+        // Change the surface to either a valid surface or a null value.
+        if (mSurface != surface && (surface == null || surface.isValid())) {
+            Log.i(TAG, "Setting the new surface from " + mSurface + " to " + surface);
             if (mVirtualDisplay != null) {
-                mVirtualDisplay.release();
-                mVirtualDisplay = null;
+                mVirtualDisplay.setSurface(surface);
             }
+            if (mSurface != null) {
+                mSurface.release();
+            }
+            mSurface = surface;
+        }
+    }
+
+    /**
+     * Starts an ImageReader as a do-nothing Surface.  The virtual display will not get fully
+     * initialized within surface flinger unless it has a valid Surface associated with it. We use
+     * the ImageReader as the default valid Surface.
+     */
+    private void startImageReader() {
+        if (mImageReader == null) {
+            mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, ImageFormat.RAW_PRIVATE,
+                2 /* maxImages */);
+        }
+        synchronized (mVdLock) {
+            setSurfaceLocked(mImageReader.getSurface());
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index ef3d87c..e60295d 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -32,9 +32,11 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Trace;
 import android.util.Slog;
 import android.view.IApplicationToken;
@@ -180,12 +182,13 @@
             IApplicationToken token, AppWindowContainerListener listener, int index,
             int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
             boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
-            int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos) {
+            int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
+            Configuration overrideConfig, Rect bounds) {
         this(taskController, token, listener, index, requestedOrientation, fullscreen,
                 showForAllUsers,
                 configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable,
                 targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos,
-                WindowManagerService.getInstance());
+                WindowManagerService.getInstance(), overrideConfig, bounds);
     }
 
     public AppWindowContainerController(TaskWindowContainerController taskController,
@@ -193,7 +196,7 @@
             int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
             boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
             int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
-            WindowManagerService service) {
+            WindowManagerService service, Configuration overrideConfig, Rect bounds) {
         super(listener, service);
         mHandler = new Handler(service.mH.getLooper());
         mToken = token;
@@ -214,7 +217,7 @@
             atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
                     inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
                     requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
-                    alwaysFocusable, this);
+                    alwaysFocusable, this, overrideConfig, bounds);
             if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
                     + " controller=" + taskController + " at " + index);
             task.addChild(atoken, index);
@@ -226,11 +229,12 @@
             boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
             boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
             int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
-            boolean alwaysFocusable, AppWindowContainerController controller) {
+            boolean alwaysFocusable, AppWindowContainerController controller,
+            Configuration overrideConfig, Rect bounds) {
         return new AppWindowToken(service, token, voiceInteraction, dc,
                 inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
                 rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
-                controller);
+                controller, overrideConfig, bounds);
     }
 
     public void removeContainer(int displayId) {
@@ -297,6 +301,17 @@
         }
     }
 
+    // TODO(b/36505427): Maybe move to WindowContainerController so other sub-classes can use it as
+    // a generic way to set override config. Need to untangle current ways the override config is
+    // currently set for tasks and displays before we are doing that though.
+    public void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+        synchronized(mWindowMap) {
+            if (mContainer != null) {
+                mContainer.onOverrideConfigurationChanged(overrideConfiguration, bounds);
+            }
+        }
+    }
+
     public void setDisablePreviewScreenshots(boolean disable) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index d1f1305..72ae90d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -164,6 +164,11 @@
     private boolean mLastContainsShowWhenLockedWindow;
     private boolean mLastContainsDismissKeyguardWindow;
 
+    // The bounds of this activity. Mainly used for aspect-ratio compatibility.
+    // TODO(b/36505427): Every level on WindowContainer now has bounds information, which directly
+    // affects the configuration. We should probably move this into that class.
+    private final Rect mBounds = new Rect();
+
     ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
     ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
 
@@ -173,8 +178,8 @@
             DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
             boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
             int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
-            AppWindowContainerController controller) {
-        this(service, token, voiceInteraction, dc, fullscreen);
+            AppWindowContainerController controller, Configuration overrideConfig, Rect bounds) {
+        this(service, token, voiceInteraction, dc, fullscreen, overrideConfig, bounds);
         setController(controller);
         mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
         mShowForAllUsers = showForAllUsers;
@@ -191,7 +196,7 @@
     }
 
     AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
-            DisplayContent dc, boolean fillsParent) {
+            DisplayContent dc, boolean fillsParent, Configuration overrideConfig, Rect bounds) {
         super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
                 false /* ownerCanManageAppTokens */);
         appToken = token;
@@ -199,6 +204,30 @@
         mFillsParent = fillsParent;
         mInputApplicationHandle = new InputApplicationHandle(this);
         mAppAnimator = new AppWindowAnimator(this, service);
+        if (overrideConfig != null) {
+            onOverrideConfigurationChanged(overrideConfig);
+        }
+        if (bounds != null) {
+            mBounds.set(bounds);
+        }
+    }
+
+    void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+        onOverrideConfigurationChanged(overrideConfiguration);
+        if (mBounds.equals(bounds)) {
+            return;
+        }
+        // TODO(b/36505427): If bounds is in WC, then we can automatically call onResize() when set.
+        mBounds.set(bounds);
+        onResize();
+    }
+
+    void getBounds(Rect outBounds) {
+        outBounds.set(mBounds);
+    }
+
+    boolean hasBounds() {
+        return !mBounds.isEmpty();
     }
 
     void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e5b00f3..8f391a7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3044,6 +3044,12 @@
         mTaskStackContainers.removeExistingAppTokensIfPossible();
     }
 
+    @Override
+    void onDescendantOverrideConfigurationChanged() {
+        setLayoutNeeded();
+        mService.requestTraversal();
+    }
+
     static final class TaskForResizePointSearchResult {
         boolean searchDone;
         Task taskForResize;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6973c3c..2a02359 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.EMPTY;
 
 /**
  * Defines common functionality for classes that can hold windows directly or through their
@@ -315,9 +316,22 @@
     void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
         mOverrideConfiguration.setTo(overrideConfiguration);
         // Update full configuration of this container and all its children.
-        onConfigurationChanged(mParent != null ? mParent.getConfiguration() : Configuration.EMPTY);
+        onConfigurationChanged(mParent != null ? mParent.getConfiguration() : EMPTY);
         // Update merged override config of this container and all its children.
         onMergedOverrideConfigurationChanged();
+
+        if (mParent != null) {
+            mParent.onDescendantOverrideConfigurationChanged();
+        }
+    }
+
+    /**
+     * Notify that a descendant's overrideConfiguration has changed.
+     */
+    void onDescendantOverrideConfigurationChanged() {
+        if (mParent != null) {
+            mParent.onDescendantOverrideConfigurationChanged();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d4c8b1f..6fd95a4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -717,16 +717,16 @@
         mHaveFrame = true;
 
         final Task task = getTask();
-        final boolean fullscreenTask = !isInMultiWindowMode();
+        final boolean inFullscreenContainer = inFullscreenContainer();
         final boolean windowsAreFloating = task != null && task.isFloating();
         final DisplayContent dc = getDisplayContent();
 
         // If the task has temp inset bounds set, we have to make sure all its windows uses
         // the temp inset frame. Otherwise different display frames get applied to the main
         // window and the child window, making them misaligned.
-        if (fullscreenTask) {
+        if (inFullscreenContainer) {
             mInsetFrame.setEmpty();
-        } else {
+        } else if (task != null && isInMultiWindowMode()) {
             task.getTempInsetBounds(mInsetFrame);
         }
 
@@ -740,7 +740,7 @@
         // The offset from the layout containing frame to the actual containing frame.
         final int layoutXDiff;
         final int layoutYDiff;
-        if (fullscreenTask || layoutInParentFrame()) {
+        if (inFullscreenContainer || layoutInParentFrame()) {
             // We use the parent frame as the containing frame for fullscreen and child windows
             mContainingFrame.set(parentFrame);
             mDisplayFrame.set(displayFrame);
@@ -749,7 +749,7 @@
             layoutXDiff = 0;
             layoutYDiff = 0;
         } else {
-            task.getBounds(mContainingFrame);
+            getContainerBounds(mContainingFrame);
             if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
 
                 // If the bounds are frozen, we still want to translate the window freely and only
@@ -883,7 +883,7 @@
                     Math.min(mStableFrame.bottom, mFrame.bottom));
         }
 
-        if (fullscreenTask && !windowsAreFloating) {
+        if (inFullscreenContainer && !windowsAreFloating) {
             // Windows that are not fullscreen can be positioned outside of the display frame,
             // but that is not a reason to provide them with overscan insets.
             mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
@@ -908,10 +908,10 @@
             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 = !windowsAreFloating && !fullscreenTask &&
-                    mFrame.right > mTmpRect.right;
-            boolean overrideBottomInset = !windowsAreFloating && !fullscreenTask &&
-                    mFrame.bottom > mTmpRect.bottom;
+            boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
+                    && mFrame.right > mTmpRect.right;
+            boolean overrideBottomInset = !windowsAreFloating && !inFullscreenContainer
+                    && mFrame.bottom > mTmpRect.bottom;
             mContentInsets.set(mContentFrame.left - mFrame.left,
                     mContentFrame.top - mFrame.top,
                     overrideRightInset ? mTmpRect.right - mContentFrame.right
@@ -3122,6 +3122,28 @@
         return task != null && !task.isFullscreen();
     }
 
+    /** Is this window in a container that takes up the entire screen space? */
+    private boolean inFullscreenContainer() {
+        if (mAppToken == null) {
+            return true;
+        }
+        if (mAppToken.hasBounds()) {
+            return false;
+        }
+        return !isInMultiWindowMode();
+    }
+
+    /** Returns the appropriate bounds to use for computing frames. */
+    private void getContainerBounds(Rect outBounds) {
+        if (isInMultiWindowMode()) {
+            getTask().getBounds(outBounds);
+        } else if (mAppToken != null){
+            mAppToken.getBounds(outBounds);
+        } else {
+            outBounds.setEmpty();
+        }
+    }
+
     boolean isDragResizeChanged() {
         return mDragResizing != computeDragResizing();
     }
@@ -3137,7 +3159,7 @@
     /**
      * @return Whether we reported a drag resize change to the application or not already.
      */
-    boolean isDragResizingChangeReported() {
+    private boolean isDragResizingChangeReported() {
         return mDragResizingChangeReported;
     }
 
@@ -3154,7 +3176,7 @@
      * Set whether we got resized but drag resizing flag was false.
      * @see #isResizedWhileNotDragResizing().
      */
-    void setResizedWhileNotDragResizing(boolean resizedWhileNotDragResizing) {
+    private void setResizedWhileNotDragResizing(boolean resizedWhileNotDragResizing) {
         mResizedWhileNotDragResizing = resizedWhileNotDragResizing;
         mResizedWhileNotDragResizingReported = !resizedWhileNotDragResizing;
     }
@@ -3459,17 +3481,17 @@
         final int pw = containingFrame.width();
         final int ph = containingFrame.height();
         final Task task = getTask();
-        final boolean nonFullscreenTask = isInMultiWindowMode();
+        final boolean inNonFullscreenContainer = !inFullscreenContainer();
         final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
 
         // We need to fit it to the display if either
-        // a) The task is fullscreen, or we don't have a task (we assume fullscreen for the taskless
-        // windows)
+        // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
+        // for the taskless windows)
         // b) If it's a secondary app window, we also need to fit it to the display unless
-        // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on screen,
-        // but SurfaceViews want to be always at a specific location so we don't fit it to the
-        // display.
-        final boolean fitToDisplay = (task == null || !nonFullscreenTask)
+        // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
+        // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
+        // the display.
+        final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
                 || ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
         float x, y;
         int w,h;
@@ -3514,7 +3536,7 @@
             y = mAttrs.y;
         }
 
-        if (nonFullscreenTask && !layoutInParentFrame()) {
+        if (inNonFullscreenContainer && !layoutInParentFrame()) {
             // Make sure window fits in containing frame since it is in a non-fullscreen task as
             // required by {@link Gravity#apply} call.
             w = Math.min(w, pw);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ecbd312..ab86966 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10088,9 +10088,11 @@
             return false;
         }
 
-        Preconditions.checkNotNull(admin);
         synchronized (this) {
-            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            if (!isCallerWithSystemUid()) {
+                Preconditions.checkNotNull(admin);
+                getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            }
             return mInjector.securityLogGetLoggingEnabledProperty();
         }
     }
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 59e698c..7b4fa87 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -16,8 +16,6 @@
 
 package android.net.ip;
 
-import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
-
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.WakeupMessage;
 
@@ -44,7 +42,6 @@
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
-import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
@@ -1031,36 +1028,15 @@
         return true;
     }
 
-    private void enableInterfaceIPv6PrivacyExtensions() {
+    private boolean startIPv6() {
+        // Set privacy extensions.
         final String PREFER_TEMPADDRS = "2";
-        NetdService.run((INetd netd) -> {
-                netd.setProcSysNet(
-                        INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr", PREFER_TEMPADDRS);
-            });
-    }
-
-    private void setInterfaceIPv6RaRtInfoMaxPlen(int plen) {
-        // Setting RIO max plen is best effort. Catch and ignore most exceptions.
         try {
             NetdService.run((INetd netd) -> {
-                    netd.setProcSysNet(
-                            INetd.IPV6, INetd.CONF, mInterfaceName, "accept_ra_rt_info_max_plen",
-                            Integer.toString(plen));
-                });
-        } catch (ServiceSpecificException e) {
-            // Old kernel versions without support for RIOs do not export accept_ra_rt_info_max_plen
-            // in the /proc filesystem. If the kernel supports RIOs we should never see any other
-            // type of error.
-            if (e.errorCode != OsConstants.ENOENT) {
-                logError("unexpected error setting accept_ra_rt_info_max_plen %s", e);
-            }
-        }
-    }
-
-    private boolean startIPv6() {
-        try {
-            enableInterfaceIPv6PrivacyExtensions();
-            setInterfaceIPv6RaRtInfoMaxPlen(RFC7421_PREFIX_LENGTH);
+                netd.setProcSysNet(
+                        INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr",
+                        PREFER_TEMPADDRS);
+            });
             mNwService.enableIpv6(mInterfaceName);
         } catch (IllegalStateException|RemoteException|ServiceSpecificException e) {
             logError("Unable to change interface settings: %s", e);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java
new file mode 100644
index 0000000..c9180a9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 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.server;
+
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.NetworkPolicyManager;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.BiFunction;
+
+/**
+ * Test class for {@link NetworkManagementInternal}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.NetworkManagementInternalTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ *     ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.NetworkManagementInternalTest -w \
+ *     com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NetworkManagementInternalTest {
+    private static final int TEST_UID = 111;
+
+    private NetworkManagementService.Injector mInjector;
+    private NetworkManagementInternal mNmi;
+
+    @Before
+    public void setUp() {
+        final NetworkManagementService service = new NetworkManagementService();
+        mInjector = service.getInjector();
+        mNmi = service.new LocalService();
+    }
+
+    @Test
+    public void testIsNetworkRestrictedForUid() {
+        // No firewall chains enabled
+        assertFalse(mNmi.isNetworkRestrictedForUid(TEST_UID));
+
+        // Restrict usage of mobile data in background
+        mInjector.setUidOnMeteredNetworkList(true, TEST_UID, true);
+        assertTrue("Should be true since mobile data usage is restricted",
+                mNmi.isNetworkRestrictedForUid(TEST_UID));
+        mInjector.reset();
+
+        // Data saver is on and uid is not whitelisted
+        mInjector.setDataSaverMode(true);
+        mInjector.setUidOnMeteredNetworkList(false, TEST_UID, false);
+        assertTrue("Should be true since data saver is on and the uid is not whitelisted",
+                mNmi.isNetworkRestrictedForUid(TEST_UID));
+        mInjector.reset();
+
+        // Data saver is on and uid is whitelisted
+        mInjector.setDataSaverMode(true);
+        mInjector.setUidOnMeteredNetworkList(false, TEST_UID, true);
+        assertFalse("Should be false since data saver is on and the uid is whitelisted",
+                mNmi.isNetworkRestrictedForUid(TEST_UID));
+        mInjector.reset();
+
+        final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
+        // Dozable chain
+        final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
+        isRestrictedForDozable.put(FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForDozable.put(FIREWALL_RULE_ALLOW, false);
+        isRestrictedForDozable.put(FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
+        // Powersaver chain
+        final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
+        isRestrictedForPowerSave.put(FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForPowerSave.put(FIREWALL_RULE_ALLOW, false);
+        isRestrictedForPowerSave.put(FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
+        // Standby chain
+        final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
+        isRestrictedForStandby.put(FIREWALL_RULE_DEFAULT, false);
+        isRestrictedForStandby.put(FIREWALL_RULE_ALLOW, false);
+        isRestrictedForStandby.put(FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
+
+        final int[] chains = {
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_DOZABLE
+        };
+        final int[] states = {
+                FIREWALL_RULE_ALLOW,
+                FIREWALL_RULE_DENY,
+                FIREWALL_RULE_DEFAULT
+        };
+        BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
+            return String.format("Unexpected value for chain: %s and state: %s",
+                    valueToString(NetworkPolicyManager.class, "FIREWALL_CHAIN_", chain),
+                    valueToString(NetworkPolicyManager.class, "FIREWALL_RULE_", state));
+        };
+        for (int chain : chains) {
+            final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
+            mInjector.setFirewallChainState(chain, true);
+            for (int state : states) {
+                mInjector.setFirewallRule(chain, TEST_UID, state);
+                assertEquals(errorMsg.apply(chain, state),
+                        expectedValues.get(state), mNmi.isNetworkRestrictedForUid(TEST_UID));
+            }
+            mInjector.reset();
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index cc5764b..b12da34 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -132,6 +132,7 @@
         mHandler = new TestHandler(mHandlerThread.getLooper());
         mInjector = new TestInjector();
         mAms = new ActivityManagerService(mInjector);
+        mAms.mWaitForNetworkTimeoutMs = 100;
     }
 
     @After
@@ -217,6 +218,17 @@
                 44, // exptectedCurProcStateSeq
                 -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
                 false); // expectNotify
+
+        // Verify when waitForNetworkTimeout is 0, then procStateSeq is not incremented.
+        mAms.mWaitForNetworkTimeoutMs = 0;
+        mInjector.setNetworkRestrictedForUid(true);
+        verifySeqCounterAndInteractions(uidRec,
+                PROCESS_STATE_TOP, // prevState
+                PROCESS_STATE_IMPORTANT_BACKGROUND, // curState
+                44, // expectedGlobalCounter
+                44, // exptectedCurProcStateSeq
+                -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+                false); // expectNotify
     }
 
     private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState,
diff --git a/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
index 4d5f783..a4e3988 100644
--- a/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java
@@ -52,6 +52,8 @@
 import java.util.Collections;
 
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -76,6 +78,12 @@
             (int) VIBRATOR_PATTERN_2};
     private static final long[] VIBRATOR_PATTERN_LONG = {VIBRATOR_PATTERN_1, VIBRATOR_PATTERN_2};
 
+    // Convenience values for enabling/disabling to make code more readable
+    private static final int DISABLED = 0;
+    private static final int ENABLED_EXCEPT_LOCK_SCREEN = 1;
+    private static final int ENABLED_INCLUDING_LOCK_SCREEN = 2;
+    private static final int DISABLED_BUT_LOCK_SCREEN_ON = 3;
+
     private @Mock Context mContext;
     private @Mock FrameworkObjectProvider mFrameworkObjectProvider;
     private @Mock IAccessibilityManager mAccessibilityManagerService;
@@ -158,38 +166,103 @@
     }
 
     @Test
-    public void testShortcutAvailable_withNullServiceIdWhenCreated_shouldReturnFalse() {
-        configureShortcutDisabled();
-        assertFalse(getController().isAccessibilityShortcutAvailable());
+    public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse() {
+        configureNoShortcutService();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        assertFalse(getController().isAccessibilityShortcutAvailable(false));
     }
 
     @Test
-    public void testShortcutAvailable_withNonNullServiceIdWhenCreated_shouldReturnTrue() {
-        configureShortcutEnabled();
-        assertTrue(getController().isAccessibilityShortcutAvailable());
+    public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue() {
+        configureValidShortcutService();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        assertTrue(getController().isAccessibilityShortcutAvailable(false));
+    }
+
+    @Test
+    public void testShortcutAvailable_disabledWithValidServiceWhenCreated_shouldReturnFalse() {
+        configureValidShortcutService();
+        configureShortcutEnabled(DISABLED_BUT_LOCK_SCREEN_ON);
+        assertFalse(getController().isAccessibilityShortcutAvailable(false));
+    }
+
+    @Test
+    public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse() {
+        configureValidShortcutService();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        assertFalse(getController().isAccessibilityShortcutAvailable(true));
+    }
+
+    @Test
+    public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue() {
+        configureValidShortcutService();
+        configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
+        assertTrue(getController().isAccessibilityShortcutAvailable(true));
     }
 
     @Test
     public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse() {
-        configureShortcutEnabled();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
         accessibilityShortcutController.onSettingsChanged();
-        assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable());
+        assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
     }
 
     @Test
     public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue() {
-        configureShortcutDisabled();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureNoShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
-        configureShortcutEnabled();
+        configureValidShortcutService();
         accessibilityShortcutController.onSettingsChanged();
-        assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable());
+        assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+    }
+
+    @Test
+    public void testShortcutAvailable_whenShortcutBecomesDisabled_shouldReturnFalse() {
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
+        AccessibilityShortcutController accessibilityShortcutController = getController();
+        configureShortcutEnabled(DISABLED);
+        accessibilityShortcutController.onSettingsChanged();
+        assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+    }
+
+    @Test
+    public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue() {
+        configureShortcutEnabled(DISABLED);
+        configureValidShortcutService();
+        AccessibilityShortcutController accessibilityShortcutController = getController();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        accessibilityShortcutController.onSettingsChanged();
+        assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+    }
+
+    @Test
+    public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse() {
+        configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
+        configureValidShortcutService();
+        AccessibilityShortcutController accessibilityShortcutController = getController();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        accessibilityShortcutController.onSettingsChanged();
+        assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
+    }
+
+    @Test
+    public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue() {
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
+        AccessibilityShortcutController accessibilityShortcutController = getController();
+        configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
+        accessibilityShortcutController.onSettingsChanged();
+        assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
     }
 
     @Test
     public void testOnAccessibilityShortcut_vibrates() {
-        configureShortcutEnabled();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         AccessibilityShortcutController accessibilityShortcutController = getController();
         accessibilityShortcutController.performAccessibilityShortcut();
         verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject());
@@ -198,7 +271,8 @@
     @Test
     public void testOnAccessibilityShortcut_firstTime_showsWarningDialog()
             throws Exception {
-        configureShortcutEnabled();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
         accessibilityShortcutController.performAccessibilityShortcut();
@@ -214,7 +288,8 @@
     @Test
     public void testOnAccessibilityShortcut_withDialogShowing_callsServer()
         throws Exception {
-        configureShortcutEnabled();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
         accessibilityShortcutController.performAccessibilityShortcut();
@@ -229,7 +304,8 @@
     @Test
     public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog()
         throws Exception {
-        configureShortcutEnabled();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
         accessibilityShortcutController.performAccessibilityShortcut();
@@ -245,7 +321,8 @@
 
     @Test
     public void testClickingDisableButtonInDialog_shouldClearShortcutId() {
-        configureShortcutEnabled();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
         getController().performAccessibilityShortcut();
 
@@ -261,7 +338,8 @@
 
     @Test
     public void testClickingLeaveOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
-        configureShortcutEnabled();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
         getController().performAccessibilityShortcut();
 
@@ -281,7 +359,8 @@
 
     @Test
     public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
-        configureShortcutEnabled();
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
         getController().performAccessibilityShortcut();
 
@@ -290,18 +369,48 @@
         verify(mAccessibilityManagerService).performAccessibilityShortcut();
     }
 
-    private void configureShortcutDisabled() {
+    private void configureNoShortcutService() {
         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
     }
 
-    private void configureShortcutEnabled() {
+    private void configureValidShortcutService() {
         Settings.Secure.putString(
                 mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
     }
 
+    private void configureShortcutEnabled(int enabledValue) {
+        final boolean enabled;
+        final boolean lockscreen;
+
+        switch (enabledValue) {
+            case DISABLED:
+                enabled = false;
+                lockscreen = false;
+                break;
+            case DISABLED_BUT_LOCK_SCREEN_ON:
+                enabled = false;
+                lockscreen = true;
+                break;
+            case ENABLED_INCLUDING_LOCK_SCREEN:
+                enabled = true;
+                lockscreen = true;
+                break;
+            case ENABLED_EXCEPT_LOCK_SCREEN:
+                enabled = true;
+                lockscreen = false;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_ENABLED, enabled ? 1 : 0);
+        Settings.Secure.putInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, lockscreen ? 1 : 0);
+    }
+
     private AccessibilityShortcutController getController() {
         AccessibilityShortcutController accessibilityShortcutController =
-                new AccessibilityShortcutController(mContext, mHandler);
+                new AccessibilityShortcutController(mContext, mHandler, 0);
         accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
         return accessibilityShortcutController;
     }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 74557e2..80b2e7d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -361,6 +361,18 @@
     }
 
     @Test
+    public void testOverrideConfigurationAncestorNotification() {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer grandparent = builder.setLayer(0).build();
+
+        final TestWindowContainer parent = grandparent.addChildWindow();
+        final TestWindowContainer child = parent.addChildWindow();
+        child.onOverrideConfigurationChanged(new Configuration());
+
+        assertTrue(grandparent.mOnDescendantOverrideCalled);
+    }
+
+    @Test
     public void testRemoveChild() throws Exception {
         final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
         final TestWindowContainer root = builder.setLayer(0).build();
@@ -718,6 +730,7 @@
         private Integer mOrientation;
 
         private boolean mOnParentSetCalled;
+        private boolean mOnDescendantOverrideCalled;
 
         /**
          * Compares 2 window layers and returns -1 if the first is lesser than the second in terms
@@ -776,6 +789,12 @@
         }
 
         @Override
+        void onDescendantOverrideConfigurationChanged() {
+            mOnDescendantOverrideCalled = true;
+            super.onDescendantOverrideConfigurationChanged();
+        }
+
+        @Override
         boolean isAnimating() {
             return mIsAnimating || super.isAnimating();
         }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 28b6e45..1b1984d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -23,7 +23,6 @@
 import android.app.ActivityManager.TaskDescription;
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -34,7 +33,6 @@
 import android.view.WindowManager;
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.FILL_PARENT;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -47,9 +45,8 @@
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
-public class WindowFrameTests {
+public class WindowFrameTests extends WindowTestsBase {
 
-    private static WindowManagerService sWm = null;
     private WindowToken mWindowToken;
     private final IWindow mIWindow = new TestIWindow();
 
@@ -105,8 +102,7 @@
         // Just any non zero value.
         sWm.mSystemDecorLayer = 10000;
 
-        mWindowToken = new WindowToken(sWm, new Binder(), 0, false,
-                sWm.getDefaultDisplayContentLocked(), false /* ownerCanManageAppTokens */);
+        mWindowToken = new TestAppWindowToken(sWm.getDefaultDisplayContentLocked());
         mStubStack = new TaskStack(sWm, 0);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 6f78245..48799d2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -303,17 +303,19 @@
     static class TestAppWindowToken extends AppWindowToken {
 
         TestAppWindowToken(DisplayContent dc) {
-            super(sWm, null, false, dc, true /* fillsParent */);
+            super(sWm, null, false, dc, true /* fillsParent */, null /* overrideConfig */,
+                    null /* bounds */);
         }
 
         TestAppWindowToken(WindowManagerService service, IApplicationToken token,
                 boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
                 boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
                 int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
-                boolean alwaysFocusable, AppWindowContainerController controller) {
+                boolean alwaysFocusable, AppWindowContainerController controller,
+                Configuration overrideConfig, Rect bounds) {
             super(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen,
                     showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges,
-                    launchTaskBehind, alwaysFocusable, controller);
+                    launchTaskBehind, alwaysFocusable, controller, overrideConfig, bounds);
         }
 
         int getWindowsCount() {
@@ -428,7 +430,8 @@
                     true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */,
                     false /* launchTaskBehind */, false /* alwaysFocusable */,
                     0 /* targetSdkVersion */, 0 /* rotationAnimationHint */,
-                    0 /* inputDispatchingTimeoutNanos */, sWm);
+                    0 /* inputDispatchingTimeoutNanos */, sWm, null /* overrideConfig */,
+                    null /* bounds */);
             mToken = token;
         }
 
@@ -437,12 +440,13 @@
                 boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
                 boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
                 int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
-                boolean alwaysFocusable, AppWindowContainerController controller) {
+                boolean alwaysFocusable, AppWindowContainerController controller,
+                Configuration overrideConfig, Rect bounds) {
             return new TestAppWindowToken(service, token, voiceInteraction, dc,
                     inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk,
                     orientation,
                     rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
-                    controller);
+                    controller, overrideConfig, bounds);
         }
 
         AppWindowToken getAppWindowToken() {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 1b28db7..e55e073 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -20,6 +20,7 @@
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.os.Build;
+import android.os.SystemProperties;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.TimeUtils;
@@ -56,6 +57,9 @@
     private static final boolean DEBUG = UsageStatsService.DEBUG;
     private static final String BAK_SUFFIX = ".bak";
     private static final String CHECKED_IN_SUFFIX = UsageStatsXml.CHECKED_IN_SUFFIX;
+    private static final String RETENTION_LEN_KEY = "ro.usagestats.chooser.retention";
+    private static final int SELECTION_LOG_RETENTION_LEN =
+            SystemProperties.getInt(RETENTION_LEN_KEY, 14);
 
     private final Object mLock = new Object();
     private final File[] mIntervalDirs;
@@ -504,7 +508,7 @@
                     mCal.getTimeInMillis());
 
             mCal.setTimeInMillis(currentTimeMillis);
-            mCal.addDays(-14);
+            mCal.addDays(-SELECTION_LOG_RETENTION_LEN);
             for (int i = 0; i < mIntervalDirs.length; ++i) {
                 pruneChooserCountsOlderThan(mIntervalDirs[i], mCal.getTimeInMillis());
             }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 4da2853..f1cf441 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -99,6 +99,11 @@
      */
     private static final String USB_STATE_PROPERTY = "sys.usb.state";
 
+    /**
+     * ro.bootmode value when phone boots into usual Android.
+     */
+    private static final String NORMAL_BOOT = "normal";
+
     private static final String USB_STATE_MATCH =
             "DEVPATH=/devices/virtual/android_usb/android0";
     private static final String ACCESSORY_START_MATCH =
@@ -157,7 +162,7 @@
     private boolean mMidiEnabled;
     private int mMidiCard;
     private int mMidiDevice;
-    private Map<String, List<Pair<String, String>>> mOemModeMap;
+    private HashMap<String, HashMap<String, Pair<String, String>>> mOemModeMap;
     private String[] mAccessoryStrings;
     private UsbDebuggingManager mDebuggingManager;
     private final UsbAlsaManager mUsbAlsaManager;
@@ -374,16 +379,32 @@
         private boolean mAdbNotificationShown;
         private int mCurrentUser = UserHandle.USER_NULL;
         private boolean mUsbCharging;
+        private String mCurrentOemFunctions;
 
         public UsbHandler(Looper looper) {
             super(looper);
             try {
                 // Restore default functions.
-                mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
-                        UsbManager.USB_FUNCTION_NONE);
-                mCurrentFunctionsApplied = mCurrentFunctions.equals(
-                        SystemProperties.get(USB_STATE_PROPERTY));
-                mAdbEnabled = UsbManager.containsFunction(getDefaultFunctions(),
+
+                if (isNormalBoot()) {
+                    mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
+                            UsbManager.USB_FUNCTION_NONE);
+                    mCurrentFunctionsApplied = mCurrentFunctions.equals(
+                            SystemProperties.get(USB_STATE_PROPERTY));
+                } else {
+                    mCurrentFunctions = SystemProperties.get(getPersistProp(true),
+                            UsbManager.USB_FUNCTION_NONE);
+                    mCurrentFunctionsApplied = SystemProperties.get(USB_CONFIG_PROPERTY,
+                            UsbManager.USB_FUNCTION_NONE).equals(
+                            SystemProperties.get(USB_STATE_PROPERTY));
+                }
+
+                /**
+                 * Use the normal bootmode persistent prop to maintain state of adb across
+                 * all boot modes.
+                 */
+                mAdbEnabled = UsbManager.containsFunction(
+                        SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY),
                         UsbManager.USB_FUNCTION_ADB);
 
                 /**
@@ -577,18 +598,36 @@
             Slog.e(TAG, "Unable to set any USB functions!");
         }
 
+        private boolean isNormalBoot() {
+            String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+            if (bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown")) {
+                return true;
+            }
+            return false;
+        }
+
         private boolean trySetEnabledFunctions(String functions, boolean forceRestart) {
             if (functions == null || applyAdbFunction(functions)
                     .equals(UsbManager.USB_FUNCTION_NONE)) {
                 functions = getDefaultFunctions();
             }
             functions = applyAdbFunction(functions);
-            functions = applyOemOverrideFunction(functions);
 
-            if (!mCurrentFunctions.equals(functions) || !mCurrentFunctionsApplied
+            String oemFunctions = applyOemOverrideFunction(functions);
+
+            if (!isNormalBoot() && !mCurrentFunctions.equals(functions)) {
+                SystemProperties.set(getPersistProp(true), functions);
+            }
+
+            if ((!functions.equals(oemFunctions) &&
+                    (mCurrentOemFunctions == null ||
+                            !mCurrentOemFunctions.equals(oemFunctions)))
+                    || !mCurrentFunctions.equals(functions)
+                    || !mCurrentFunctionsApplied
                     || forceRestart) {
                 Slog.i(TAG, "Setting USB config to " + functions);
                 mCurrentFunctions = functions;
+                mCurrentOemFunctions = oemFunctions;
                 mCurrentFunctionsApplied = false;
 
                 // Kick the USB stack to close existing connections.
@@ -600,12 +639,12 @@
                 }
 
                 // Set the new USB configuration.
-                setUsbConfig(functions);
+                setUsbConfig(oemFunctions);
 
                 // Start up dependent services.
                 updateUsbStateBroadcastIfNeeded(true);
 
-                if (!waitForState(functions)) {
+                if (!waitForState(oemFunctions)) {
                     Slog.e(TAG, "Failed to switch USB config to " + functions);
                     return false;
                 }
@@ -616,6 +655,11 @@
         }
 
         private String applyAdbFunction(String functions) {
+            // Do not pass null pointer to the UsbManager.
+            // There isnt a check there.
+            if (functions == null) {
+                functions = "";
+            }
             if (mAdbEnabled) {
                 functions = UsbManager.addFunction(functions, UsbManager.USB_FUNCTION_ADB);
             } else {
@@ -1010,7 +1054,7 @@
         }
 
         private String getDefaultFunctions() {
-            String func = SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY,
+            String func = SystemProperties.get(getPersistProp(true),
                     UsbManager.USB_FUNCTION_NONE);
             if (UsbManager.USB_FUNCTION_NONE.equals(func)) {
                 func = UsbManager.USB_FUNCTION_MTP;
@@ -1021,6 +1065,7 @@
         public void dump(IndentingPrintWriter pw) {
             pw.println("USB Device State:");
             pw.println("  mCurrentFunctions: " + mCurrentFunctions);
+            pw.println("  mCurrentOemFunctions: " + mCurrentOemFunctions);
             pw.println("  mCurrentFunctionsApplied: " + mCurrentFunctionsApplied);
             pw.println("  mConnected: " + mConnected);
             pw.println("  mConfigured: " + mConfigured);
@@ -1082,39 +1127,99 @@
         if (configList != null) {
             for (String config : configList) {
                 String[] items = config.split(":");
-                if (items.length == 3) {
+                if (items.length == 3 || items.length == 4) {
                     if (mOemModeMap == null) {
-                        mOemModeMap = new HashMap<String, List<Pair<String, String>>>();
+                        mOemModeMap = new HashMap<String, HashMap<String,
+                                Pair<String, String>>>();
                     }
-                    List<Pair<String, String>> overrideList = mOemModeMap.get(items[0]);
-                    if (overrideList == null) {
-                        overrideList = new LinkedList<Pair<String, String>>();
-                        mOemModeMap.put(items[0], overrideList);
+                    HashMap<String, Pair<String, String>> overrideMap
+                            = mOemModeMap.get(items[0]);
+                    if (overrideMap == null) {
+                        overrideMap = new HashMap<String,
+                                Pair<String, String>>();
+                        mOemModeMap.put(items[0], overrideMap);
                     }
-                    overrideList.add(new Pair<String, String>(items[1], items[2]));
+
+                    // Favoring the first combination if duplicate exists
+                    if (!overrideMap.containsKey(items[1])) {
+                        if (items.length == 3) {
+                            overrideMap.put(items[1],
+                                    new Pair<String, String>(items[2], ""));
+                        } else {
+                            overrideMap.put(items[1],
+                                    new Pair<String, String>(items[2], items[3]));
+                        }
+                    }
                 }
             }
         }
     }
 
     private String applyOemOverrideFunction(String usbFunctions) {
-        if ((usbFunctions == null) || (mOemModeMap == null)) return usbFunctions;
+        if ((usbFunctions == null) || (mOemModeMap == null)) {
+            return usbFunctions;
+        }
 
         String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+        Slog.d(TAG, "applyOemOverride usbfunctions=" + usbFunctions + " bootmode=" + bootMode);
 
-        List<Pair<String, String>> overrides = mOemModeMap.get(bootMode);
-        if (overrides != null) {
-            for (Pair<String, String> pair : overrides) {
-                if (pair.first.equals(usbFunctions)) {
-                    Slog.d(TAG, "OEM USB override: " + pair.first + " ==> " + pair.second);
-                    return pair.second;
+        Map<String, Pair<String, String>> overridesMap =
+                mOemModeMap.get(bootMode);
+        // Check to ensure that the oem is not overriding in the normal
+        // boot mode
+        if (overridesMap != null && !(bootMode.equals(NORMAL_BOOT) ||
+                bootMode.equals("unknown"))) {
+            Pair<String, String> overrideFunctions =
+                    overridesMap.get(usbFunctions);
+            if (overrideFunctions != null) {
+                Slog.d(TAG, "OEM USB override: " + usbFunctions
+                        + " ==> " + overrideFunctions.first
+                        + " persist across reboot "
+                        + overrideFunctions.second);
+                if (!overrideFunctions.second.equals("")) {
+                    String newFunction;
+                    if (mAdbEnabled) {
+                        newFunction = UsbManager.addFunction(overrideFunctions.second,
+                                UsbManager.USB_FUNCTION_ADB);
+                    } else {
+                        newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
+                                UsbManager.USB_FUNCTION_ADB);
+                    }
+                    Slog.d(TAG, "OEM USB override persisting: " + newFunction + "in prop: "
+                            + UsbDeviceManager.getPersistProp(false));
+                    SystemProperties.set(UsbDeviceManager.getPersistProp(false),
+                            newFunction);
                 }
+                return overrideFunctions.first;
+            } else if (mAdbEnabled) {
+                String newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
+                        UsbManager.USB_FUNCTION_ADB);
+                SystemProperties.set(UsbDeviceManager.getPersistProp(false),
+                        newFunction);
+            } else {
+                SystemProperties.set(UsbDeviceManager.getPersistProp(false),
+                        UsbManager.USB_FUNCTION_NONE);
             }
         }
         // return passed in functions as is.
         return usbFunctions;
     }
 
+    public static String getPersistProp(boolean functions) {
+        String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+        String persistProp = USB_PERSISTENT_CONFIG_PROPERTY;
+        if (!(bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown"))) {
+            if (functions == true) {
+                persistProp = "persist.sys.usb." + bootMode + ".func";
+            } else {
+                persistProp = "persist.sys.usb." + bootMode + ".config";
+            }
+        }
+
+        return persistProp;
+    }
+
+
     public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
         if (mDebuggingManager != null) {
             mDebuggingManager.allowUsbDebugging(alwaysAllow, publicKey);
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 57036aa..ef3797c 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -87,6 +87,7 @@
         "flatten/Archive.cpp",
         "flatten/TableFlattener.cpp",
         "flatten/XmlFlattener.cpp",
+        "io/BigBufferStreams.cpp",
         "io/File.cpp",
         "io/FileSystem.cpp",
         "io/Io.cpp",
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 1d04b35..b855f8f 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -20,6 +20,7 @@
 #include "ValueVisitor.h"
 #include "flatten/Archive.h"
 #include "flatten/TableFlattener.h"
+#include "io/BigBufferInputStream.h"
 
 namespace aapt {
 
@@ -27,8 +28,7 @@
                                                       const android::StringPiece& path) {
   Source source(path);
   std::string error;
-  std::unique_ptr<io::ZipFileCollection> apk =
-      io::ZipFileCollection::Create(path, &error);
+  std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
   if (!apk) {
     context->GetDiagnostics()->Error(DiagMessage(source) << error);
     return {};
@@ -36,21 +36,18 @@
 
   io::IFile* file = apk->FindFile("resources.arsc");
   if (!file) {
-    context->GetDiagnostics()->Error(DiagMessage(source)
-                                     << "no resources.arsc found");
+    context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found");
     return {};
   }
 
   std::unique_ptr<io::IData> data = file->OpenAsData();
   if (!data) {
-    context->GetDiagnostics()->Error(DiagMessage(source)
-                                     << "could not open resources.arsc");
+    context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc");
     return {};
   }
 
   std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-  BinaryResourceParser parser(context, table.get(), source, data->data(),
-                              data->size());
+  BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
   if (!parser.Parse()) {
     return {};
   }
@@ -92,9 +89,9 @@
       continue;
     }
 
-    // The resource table needs to be reserialized since it might have changed.
+    // The resource table needs to be re-serialized since it might have changed.
     if (path == "resources.arsc") {
-      BigBuffer buffer = BigBuffer(1024);
+      BigBuffer buffer(4096);
       // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
       // with sparse entries) b/35389232.
       TableFlattener flattener(options, &buffer);
@@ -102,8 +99,8 @@
         return false;
       }
 
-      if (!writer->StartEntry(path, ArchiveEntry::kAlign) || !writer->WriteEntry(buffer) ||
-          !writer->FinishEntry()) {
+      io::BigBufferInputStream input_stream(&buffer);
+      if (!writer->WriteFile(path, ArchiveEntry::kAlign, &input_stream)) {
         context->GetDiagnostics()->Error(DiagMessage()
                                          << "Error when writing file '" << path << "' in APK.");
         return false;
@@ -113,14 +110,12 @@
 
     std::unique_ptr<io::IData> data = file->OpenAsData();
     uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
-    if (!writer->StartEntry(path, compression_flags) ||
-        !writer->WriteEntry(data->data(), data->size()) || !writer->FinishEntry()) {
+    if (!writer->WriteFile(path, compression_flags, data.get())) {
       context->GetDiagnostics()->Error(DiagMessage()
                                        << "Error when writing file '" << path << "' in APK.");
       return false;
     }
   }
-
   return true;
 }
 
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 456f686..5e9b81a 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -25,7 +25,7 @@
 static const char* sMajorVersion = "2";
 
 // Update minor version whenever a feature or flag is added.
-static const char* sMinorVersion = "10";
+static const char* sMinorVersion = "11";
 
 int PrintVersion() {
   std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 8027f42..1fe30f0 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -37,6 +37,7 @@
 #include "compile/XmlIdCollector.h"
 #include "flatten/Archive.h"
 #include "flatten/XmlFlattener.h"
+#include "io/BigBufferOutputStream.h"
 #include "proto/ProtoSerialize.h"
 #include "util/Files.h"
 #include "util/Maybe.h"
@@ -46,7 +47,6 @@
 
 using android::StringPiece;
 using google::protobuf::io::CopyingOutputStreamAdaptor;
-using google::protobuf::io::ZeroCopyOutputStream;
 
 namespace aapt {
 
@@ -142,10 +142,10 @@
     IAaptContext* context, const CompileOptions& options,
     std::vector<ResourcePathData>* out_path_data) {
   const std::string& root_dir = options.res_dir.value();
-  std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()),
-                                              closedir);
+  std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
   if (!d) {
-    context->GetDiagnostics()->Error(DiagMessage() << strerror(errno));
+    context->GetDiagnostics()->Error(DiagMessage()
+                                     << android::base::SystemErrorCodeToString(errno));
     return false;
   }
 
@@ -161,10 +161,10 @@
       continue;
     }
 
-    std::unique_ptr<DIR, decltype(closedir)*> subdir(
-        opendir(prefix_path.data()), closedir);
+    std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
     if (!subdir) {
-      context->GetDiagnostics()->Error(DiagMessage() << strerror(errno));
+      context->GetDiagnostics()->Error(DiagMessage()
+                                       << android::base::SystemErrorCodeToString(errno));
       return false;
     }
 
@@ -177,8 +177,7 @@
       file::AppendPath(&full_path, leaf_entry->d_name);
 
       std::string err_str;
-      Maybe<ResourcePathData> path_data =
-          ExtractResourcePathData(full_path, &err_str);
+      Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str);
       if (!path_data) {
         context->GetDiagnostics()->Error(DiagMessage() << err_str);
         return false;
@@ -199,7 +198,7 @@
     std::ifstream fin(path_data.source.path, std::ifstream::binary);
     if (!fin) {
       context->GetDiagnostics()->Error(DiagMessage(path_data.source)
-                                       << strerror(errno));
+                                       << android::base::SystemErrorCodeToString(errno));
       return false;
     }
 
@@ -249,8 +248,7 @@
 
   // Create the file/zip entry.
   if (!writer->StartEntry(output_path, 0)) {
-    context->GetDiagnostics()->Error(DiagMessage(output_path)
-                                     << "failed to open");
+    context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open");
     return false;
   }
 
@@ -258,21 +256,18 @@
   // writer->FinishEntry().
   {
     // Wrap our IArchiveWriter with an adaptor that implements the
-    // ZeroCopyOutputStream
-    // interface.
+    // ZeroCopyOutputStream interface.
     CopyingOutputStreamAdaptor copying_adaptor(writer);
 
     std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table);
     if (!pb_table->SerializeToZeroCopyStream(&copying_adaptor)) {
-      context->GetDiagnostics()->Error(DiagMessage(output_path)
-                                       << "failed to write");
+      context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
       return false;
     }
   }
 
   if (!writer->FinishEntry()) {
-    context->GetDiagnostics()->Error(DiagMessage(output_path)
-                                     << "failed to finish entry");
+    context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
     return false;
   }
   return true;
@@ -293,16 +288,14 @@
   // writer->FinishEntry().
   {
     // Wrap our IArchiveWriter with an adaptor that implements the
-    // ZeroCopyOutputStream
-    // interface.
+    // ZeroCopyOutputStream interface.
     CopyingOutputStreamAdaptor copying_adaptor(writer);
     CompiledFileOutputStream output_stream(&copying_adaptor);
 
     // Number of CompiledFiles.
     output_stream.WriteLittleEndian32(1);
 
-    std::unique_ptr<pb::CompiledFile> compiled_file =
-        SerializeCompiledFileToPb(file);
+    std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);
     output_stream.WriteCompiledFile(compiled_file.get());
     output_stream.WriteData(&buffer);
 
@@ -371,14 +364,12 @@
     return false;
   }
 
-  std::unique_ptr<pb::CompiledFile> pb_compiled_file =
-      SerializeCompiledFileToPb(xmlres->file);
+  std::unique_ptr<pb::CompiledFile> pb_compiled_file = SerializeCompiledFileToPb(xmlres->file);
   out->WriteCompiledFile(pb_compiled_file.get());
   out->WriteData(&buffer);
 
   if (out->HadError()) {
-    context->GetDiagnostics()->Error(DiagMessage(output_path)
-                                     << "failed to write data");
+    context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write data");
     return false;
   }
   return true;
@@ -388,8 +379,7 @@
                        const ResourcePathData& path_data,
                        IArchiveWriter* writer, const std::string& output_path) {
   if (context->IsVerbose()) {
-    context->GetDiagnostics()->Note(DiagMessage(path_data.source)
-                                    << "compiling XML");
+    context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
   }
 
   std::unique_ptr<xml::XmlResource> xmlres;
@@ -397,7 +387,7 @@
     std::ifstream fin(path_data.source.path, std::ifstream::binary);
     if (!fin) {
       context->GetDiagnostics()->Error(DiagMessage(path_data.source)
-                                       << strerror(errno));
+                                       << android::base::SystemErrorCodeToString(errno));
       return false;
     }
 
@@ -470,31 +460,6 @@
   return true;
 }
 
-class BigBufferOutputStream : public io::OutputStream {
- public:
-  explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
-
-  bool Next(void** data, int* len) override {
-    size_t count;
-    *data = buffer_->NextBlock(&count);
-    *len = static_cast<int>(count);
-    return true;
-  }
-
-  void BackUp(int count) override { buffer_->BackUp(count); }
-
-  google::protobuf::int64 ByteCount() const override {
-    return buffer_->size();
-  }
-
-  bool HadError() const override { return false; }
-
- private:
-  BigBuffer* buffer_;
-
-  DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
-};
-
 static bool CompilePng(IAaptContext* context, const CompileOptions& options,
                        const ResourcePathData& path_data,
                        IArchiveWriter* writer, const std::string& output_path) {
@@ -520,7 +485,7 @@
     }
 
     BigBuffer crunched_png_buffer(4096);
-    BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
+    io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
 
     // Ensure that we only keep the chunks we care about if we end up
     // using the original PNG instead of the crunched one.
@@ -533,8 +498,7 @@
     std::unique_ptr<NinePatch> nine_patch;
     if (path_data.extension == "9.png") {
       std::string err;
-      nine_patch = NinePatch::Create(image->rows.get(), image->width,
-                                     image->height, &err);
+      nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
       if (!nine_patch) {
         context->GetDiagnostics()->Error(DiagMessage() << err);
         return false;
@@ -547,8 +511,7 @@
       // width - 2.
       image->width -= 2;
       image->height -= 2;
-      memmove(image->rows.get(), image->rows.get() + 1,
-              image->height * sizeof(uint8_t**));
+      memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
       for (int32_t h = 0; h < image->height; h++) {
         memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
       }
@@ -560,8 +523,7 @@
     }
 
     // Write the crunched PNG.
-    if (!WritePng(context, image.get(), nine_patch.get(),
-                  &crunched_png_buffer_out, {})) {
+    if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
       return false;
     }
 
@@ -574,24 +536,21 @@
       // The re-encoded PNG is larger than the original, and there is
       // no mandatory transformation. Use the original.
       if (context->IsVerbose()) {
-        context->GetDiagnostics()->Note(
-            DiagMessage(path_data.source)
-            << "original PNG is smaller than crunched PNG"
-            << ", using original");
+        context->GetDiagnostics()->Note(DiagMessage(path_data.source)
+                                        << "original PNG is smaller than crunched PNG"
+                                        << ", using original");
       }
 
-      PngChunkFilter png_chunk_filter_again(content);
+      png_chunk_filter.Rewind();
       BigBuffer filtered_png_buffer(4096);
-      BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
-      io::Copy(&filtered_png_buffer_out, &png_chunk_filter_again);
+      io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
+      io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
       buffer.AppendBuffer(std::move(filtered_png_buffer));
     }
 
     if (context->IsVerbose()) {
-      // For debugging only, use the legacy PNG cruncher and compare the
-      // resulting file sizes.
-      // This will help catch exotic cases where the new code may generate
-      // larger PNGs.
+      // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
+      // This will help catch exotic cases where the new code may generate larger PNGs.
       std::stringstream legacy_stream(content);
       BigBuffer legacy_buffer(4096);
       Png png(context->GetDiagnostics());
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 5e15c88..6d6147d 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -33,7 +33,6 @@
 namespace aapt {
 
 constexpr bool kDebug = false;
-constexpr size_t kPngSignatureSize = 8u;
 
 struct PngInfo {
   ~PngInfo() {
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index a820051..e4255e7 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -31,6 +31,9 @@
 
 namespace aapt {
 
+// Size in bytes of the PNG signature.
+constexpr size_t kPngSignatureSize = 8u;
+
 struct PngOptions {
   int grayscale_tolerance = 0;
 };
@@ -46,9 +49,9 @@
                const PngOptions& options);
 
  private:
-  IDiagnostics* mDiag;
-
   DISALLOW_COPY_AND_ASSIGN(Png);
+
+  IDiagnostics* mDiag;
 };
 
 /**
@@ -57,26 +60,26 @@
 class PngChunkFilter : public io::InputStream {
  public:
   explicit PngChunkFilter(const android::StringPiece& data);
+  virtual ~PngChunkFilter() = default;
 
-  bool Next(const void** buffer, int* len) override;
-  void BackUp(int count) override;
-  bool Skip(int count) override;
+  bool Next(const void** buffer, size_t* len) override;
+  void BackUp(size_t count) override;
 
-  google::protobuf::int64 ByteCount() const override {
-    return static_cast<google::protobuf::int64>(window_start_);
-  }
+  bool CanRewind() const override { return true; }
+  bool Rewind() override;
+  size_t ByteCount() const override { return window_start_; }
 
   bool HadError() const override { return error_; }
 
  private:
-  bool ConsumeWindow(const void** buffer, int* len);
+  DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
+
+  bool ConsumeWindow(const void** buffer, size_t* len);
 
   android::StringPiece data_;
   size_t window_start_ = 0;
   size_t window_end_ = 0;
   bool error_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
 };
 
 /**
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index edec123..f9043b5 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -71,16 +71,16 @@
 PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
   if (util::StartsWith(data_, kPngSignature)) {
     window_start_ = 0;
-    window_end_ = strlen(kPngSignature);
+    window_end_ = kPngSignatureSize;
   } else {
     error_ = true;
   }
 }
 
-bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) {
+bool PngChunkFilter::ConsumeWindow(const void** buffer, size_t* len) {
   if (window_start_ != window_end_) {
     // We have bytes to give from our window.
-    const int bytes_read = (int)(window_end_ - window_start_);
+    const size_t bytes_read = window_end_ - window_start_;
     *buffer = data_.data() + window_start_;
     *len = bytes_read;
     window_start_ = window_end_;
@@ -89,7 +89,7 @@
   return false;
 }
 
-bool PngChunkFilter::Next(const void** buffer, int* len) {
+bool PngChunkFilter::Next(const void** buffer, size_t* len) {
   if (error_) {
     return false;
   }
@@ -113,16 +113,14 @@
 
     // Verify the chunk length.
     const uint32_t chunk_len = Peek32LE(data_.data() + window_end_);
-    if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) >
-        data_.size()) {
+    if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) > data_.size()) {
       // Overflow.
       error_ = true;
       return false;
     }
 
     // Do we strip this chunk?
-    const uint32_t chunk_type =
-        Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
+    const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
     if (IsPngChunkWhitelisted(chunk_type)) {
       // Advance the window to include this chunk.
       window_end_ += kMinChunkHeaderSize + chunk_len;
@@ -146,31 +144,19 @@
   return false;
 }
 
-void PngChunkFilter::BackUp(int count) {
+void PngChunkFilter::BackUp(size_t count) {
   if (error_) {
     return;
   }
   window_start_ -= count;
 }
 
-bool PngChunkFilter::Skip(int count) {
+bool PngChunkFilter::Rewind() {
   if (error_) {
     return false;
   }
-
-  const void* buffer;
-  int len;
-  while (count > 0) {
-    if (!Next(&buffer, &len)) {
-      return false;
-    }
-    if (len > count) {
-      BackUp(len - count);
-      count = 0;
-    } else {
-      count -= len;
-    }
-  }
+  window_start_ = 0;
+  window_end_ = kPngSignatureSize;
   return true;
 }
 
diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
index 3b46d8b..ae98afc 100644
--- a/tools/aapt2/compile/PngCrunch.cpp
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -29,12 +29,7 @@
 
 namespace aapt {
 
-// Size in bytes of the PNG signature.
-constexpr size_t kPngSignatureSize = 8u;
-
-/**
- * Custom deleter that destroys libpng read and info structs.
- */
+// Custom deleter that destroys libpng read and info structs.
 class PngReadStructDeleter {
  public:
   PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
@@ -51,9 +46,7 @@
   DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
 };
 
-/**
- * Custom deleter that destroys libpng write and info structs.
- */
+// Custom deleter that destroys libpng write and info structs.
 class PngWriteStructDeleter {
  public:
   PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
@@ -82,12 +75,11 @@
   diag->Error(DiagMessage() << error_msg);
 }
 
-static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer,
-                               png_size_t len) {
+static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
   io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr);
 
   const void* in_buffer;
-  int in_len;
+  size_t in_len;
   if (!in->Next(&in_buffer, &in_len)) {
     if (in->HadError()) {
       std::string err = in->GetError();
@@ -96,19 +88,18 @@
     return;
   }
 
-  const size_t bytes_read = std::min(static_cast<size_t>(in_len), len);
+  const size_t bytes_read = std::min(in_len, len);
   memcpy(buffer, in_buffer, bytes_read);
-  if (bytes_read != static_cast<size_t>(in_len)) {
-    in->BackUp(in_len - static_cast<int>(bytes_read));
+  if (bytes_read != in_len) {
+    in->BackUp(in_len - bytes_read);
   }
 }
 
-static void WriteDataToStream(png_structp png_ptr, png_bytep buffer,
-                              png_size_t len) {
+static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
   io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr);
 
   void* out_buffer;
-  int out_len;
+  size_t out_len;
   while (len > 0) {
     if (!out->Next(&out_buffer, &out_len)) {
       if (out->HadError()) {
@@ -118,7 +109,7 @@
       return;
     }
 
-    const size_t bytes_written = std::min(static_cast<size_t>(out_len), len);
+    const size_t bytes_written = std::min(out_len, len);
     memcpy(out_buffer, buffer, bytes_written);
 
     // Advance the input buffer.
@@ -126,7 +117,7 @@
     len -= bytes_written;
 
     // Advance the output buffer.
-    out_len -= static_cast<int>(bytes_written);
+    out_len -= bytes_written;
   }
 
   // If the entire output buffer wasn't used, backup.
@@ -139,41 +130,35 @@
   // Read the first 8 bytes of the file looking for the PNG signature.
   // Bail early if it does not match.
   const png_byte* signature;
-  int buffer_size;
+  size_t buffer_size;
   if (!in->Next((const void**)&signature, &buffer_size)) {
-    context->GetDiagnostics()->Error(
-        DiagMessage() << android::base::SystemErrorCodeToString(errno));
+    context->GetDiagnostics()->Error(DiagMessage()
+                                     << android::base::SystemErrorCodeToString(errno));
     return {};
   }
 
-  if (static_cast<size_t>(buffer_size) < kPngSignatureSize ||
-      png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
-    context->GetDiagnostics()->Error(
-        DiagMessage() << "file signature does not match PNG signature");
+  if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+    context->GetDiagnostics()->Error(DiagMessage()
+                                     << "file signature does not match PNG signature");
     return {};
   }
 
   // Start at the beginning of the first chunk.
-  in->BackUp(buffer_size - static_cast<int>(kPngSignatureSize));
+  in->BackUp(buffer_size - kPngSignatureSize);
 
-  // Create and initialize the png_struct with the default error and warning
-  // handlers.
-  // The header version is also passed in to ensure that this was built against
-  // the same
+  // Create and initialize the png_struct with the default error and warning handlers.
+  // The header version is also passed in to ensure that this was built against the same
   // version of libpng.
-  png_structp read_ptr =
-      png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+  png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
   if (read_ptr == nullptr) {
-    context->GetDiagnostics()->Error(
-        DiagMessage() << "failed to create libpng read png_struct");
+    context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_struct");
     return {};
   }
 
   // Create and initialize the memory for image header and data.
   png_infop info_ptr = png_create_info_struct(read_ptr);
   if (info_ptr == nullptr) {
-    context->GetDiagnostics()->Error(
-        DiagMessage() << "failed to create libpng read png_info");
+    context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_info");
     png_destroy_read_struct(&read_ptr, nullptr, nullptr);
     return {};
   }
@@ -189,8 +174,7 @@
   }
 
   // Handle warnings ourselves via IDiagnostics.
-  png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError,
-                   LogWarning);
+  png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning);
 
   // Set up the read functions which read from our custom data sources.
   png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
@@ -203,8 +187,7 @@
 
   // Extract image meta-data from the various chunk headers.
   uint32_t width, height;
-  int bit_depth, color_type, interlace_method, compression_method,
-      filter_method;
+  int bit_depth, color_type, interlace_method, compression_method, filter_method;
   png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
                &interlace_method, &compression_method, &filter_method);
 
@@ -247,11 +230,9 @@
   // 9-patch uses int32_t to index images, so we cap the image dimensions to
   // something
   // that can always be represented by 9-patch.
-  if (width > std::numeric_limits<int32_t>::max() ||
-      height > std::numeric_limits<int32_t>::max()) {
-    context->GetDiagnostics()->Error(DiagMessage()
-                                     << "PNG image dimensions are too large: "
-                                     << width << "x" << height);
+  if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) {
+    context->GetDiagnostics()->Error(
+        DiagMessage() << "PNG image dimensions are too large: " << width << "x" << height);
     return {};
   }
 
@@ -263,8 +244,7 @@
   CHECK(row_bytes == 4 * width);  // RGBA
 
   // Allocate one large block to hold the image.
-  output_image->data =
-      std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
+  output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
 
   // Create an array of rows that index into the data block.
   output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
@@ -281,19 +261,13 @@
   return output_image;
 }
 
-/**
- * Experimentally chosen constant to be added to the overhead of using color
- * type
- * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette
- * chunk.
- * Without this, many small PNGs encoded with palettes are larger after
- * compression than
- * the same PNGs encoded as RGBA.
- */
+// Experimentally chosen constant to be added to the overhead of using color type
+// PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk.
+// Without this, many small PNGs encoded with palettes are larger after compression than
+// the same PNGs encoded as RGBA.
 constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
 
-// Pick a color type by which to encode the image, based on which color type
-// will take
+// Pick a color type by which to encode the image, based on which color type will take
 // the least amount of disk space.
 //
 // 9-patch images traditionally have not been encoded with palettes.
@@ -372,20 +346,17 @@
   return PNG_COLOR_TYPE_RGBA;
 }
 
-// Assigns indices to the color and alpha palettes, encodes them, and then
-// invokes
+// Assigns indices to the color and alpha palettes, encodes them, and then invokes
 // png_set_PLTE/png_set_tRNS.
 // This must be done before writing image data.
-// Image data must be transformed to use the indices assigned within the
-// palette.
+// Image data must be transformed to use the indices assigned within the palette.
 static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
                          std::unordered_map<uint32_t, int>* color_palette,
                          std::unordered_set<uint32_t>* alpha_palette) {
   CHECK(color_palette->size() <= 256);
   CHECK(alpha_palette->size() <= 256);
 
-  // Populate the PNG palette struct and assign indices to the color
-  // palette.
+  // Populate the PNG palette struct and assign indices to the color palette.
 
   // Colors in the alpha palette should have smaller indices.
   // This will ensure that we can truncate the alpha palette if it is
@@ -403,13 +374,11 @@
   }
 
   // Create the PNG color palette struct.
-  auto color_palette_bytes =
-      std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
+  auto color_palette_bytes = std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
 
   std::unique_ptr<png_byte[]> alpha_palette_bytes;
   if (!alpha_palette->empty()) {
-    alpha_palette_bytes =
-        std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
+    alpha_palette_bytes = std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
   }
 
   for (const auto& entry : *color_palette) {
@@ -433,23 +402,20 @@
   // The bytes get copied here, so it is safe to release color_palette_bytes at
   // the end of function
   // scope.
-  png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(),
-               color_palette->size());
+  png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), color_palette->size());
 
   if (alpha_palette_bytes) {
-    png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(),
-                 alpha_palette->size(), nullptr);
+    png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), alpha_palette->size(),
+                 nullptr);
   }
 }
 
 // Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
-// before
-// writing image data.
+// before writing image data.
 static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
                            const NinePatch* nine_patch) {
   // The order of the chunks is important.
-  // 9-patch code in older platforms expects the 9-patch chunk to
-  // be last.
+  // 9-patch code in older platforms expects the 9-patch chunk to be last.
 
   png_unknown_chunk unknown_chunks[3];
   memset(unknown_chunks, 0, sizeof(unknown_chunks));
@@ -475,8 +441,7 @@
     index++;
   }
 
-  std::unique_ptr<uint8_t[]> serialized_nine_patch =
-      nine_patch->SerializeBase(&chunk_len);
+  std::unique_ptr<uint8_t[]> serialized_nine_patch = nine_patch->SerializeBase(&chunk_len);
   strcpy((char*)unknown_chunks[index].name, "npTc");
   unknown_chunks[index].size = chunk_len;
   unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
@@ -497,22 +462,18 @@
               const PngOptions& options) {
   // Create and initialize the write png_struct with the default error and
   // warning handlers.
-  // The header version is also passed in to ensure that this was built against
-  // the same
+  // The header version is also passed in to ensure that this was built against the same
   // version of libpng.
-  png_structp write_ptr =
-      png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+  png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
   if (write_ptr == nullptr) {
-    context->GetDiagnostics()->Error(
-        DiagMessage() << "failed to create libpng write png_struct");
+    context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_struct");
     return false;
   }
 
   // Allocate memory to store image header data.
   png_infop write_info_ptr = png_create_info_struct(write_ptr);
   if (write_info_ptr == nullptr) {
-    context->GetDiagnostics()->Error(
-        DiagMessage() << "failed to create libpng write png_info");
+    context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_info");
     png_destroy_write_struct(&write_ptr, nullptr);
     return false;
   }
@@ -527,8 +488,7 @@
   }
 
   // Handle warnings with our IDiagnostics.
-  png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError,
-                   LogWarning);
+  png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning);
 
   // Set up the write functions which write to our custom data sources.
   png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
@@ -599,8 +559,7 @@
     context->GetDiagnostics()->Note(msg);
   }
 
-  const bool convertible_to_grayscale =
-      max_gray_deviation <= options.grayscale_tolerance;
+  const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance;
 
   const int new_color_type = PickColorType(
       image->width, image->height, grayscale, convertible_to_grayscale,
@@ -715,15 +674,12 @@
       }
       png_write_row(write_ptr, out_row.get());
     }
-  } else if (new_color_type == PNG_COLOR_TYPE_RGB ||
-             new_color_type == PNG_COLOR_TYPE_RGBA) {
+  } else if (new_color_type == PNG_COLOR_TYPE_RGB || new_color_type == PNG_COLOR_TYPE_RGBA) {
     const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
     if (needs_to_zero_rgb_channels_of_transparent_pixels) {
       // The source RGBA data can't be used as-is, because we need to zero out
-      // the RGB
-      // values of transparent pixels.
-      auto out_row =
-          std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+      // the RGB values of transparent pixels.
+      auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
 
       for (int32_t y = 0; y < image->height; y++) {
         png_const_bytep in_row = image->rows[y];
@@ -747,8 +703,7 @@
       }
     } else {
       // The source image can be used as-is, just tell libpng whether or not to
-      // ignore
-      // the alpha channel.
+      // ignore the alpha channel.
       if (new_color_type == PNG_COLOR_TYPE_RGB) {
         // Delete the extraneous alpha values that we appended to our buffer
         // when reading the original values.
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 5c96a4d..826f91b 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -21,6 +21,7 @@
 #include <string>
 #include <vector>
 
+#include "android-base/errors.h"
 #include "android-base/macros.h"
 #include "androidfw/StringPiece.h"
 #include "ziparchive/zip_writer.h"
@@ -37,14 +38,14 @@
  public:
   DirectoryWriter() = default;
 
-  bool Open(IDiagnostics* diag, const StringPiece& out_dir) {
+  bool Open(const StringPiece& out_dir) {
     dir_ = out_dir.to_string();
     file::FileType type = file::GetFileType(dir_);
     if (type == file::FileType::kNonexistant) {
-      diag->Error(DiagMessage() << "directory " << dir_ << " does not exist");
+      error_ = "directory does not exist";
       return false;
     } else if (type != file::FileType::kDirectory) {
-      diag->Error(DiagMessage() << dir_ << " is not a directory");
+      error_ = "not a directory";
       return false;
     }
     return true;
@@ -61,27 +62,19 @@
 
     file_ = {fopen(full_path.data(), "wb"), fclose};
     if (!file_) {
+      error_ = android::base::SystemErrorCodeToString(errno);
       return false;
     }
     return true;
   }
 
-  bool WriteEntry(const BigBuffer& buffer) override {
+  bool Write(const void* data, int len) override {
     if (!file_) {
       return false;
     }
 
-    for (const BigBuffer::Block& b : buffer) {
-      if (fwrite(b.buffer.get(), 1, b.size, file_.get()) != b.size) {
-        file_.reset(nullptr);
-        return false;
-      }
-    }
-    return true;
-  }
-
-  bool WriteEntry(const void* data, size_t len) override {
-    if (fwrite(data, 1, len, file_.get()) != len) {
+    if (fwrite(data, 1, len, file_.get()) != static_cast<size_t>(len)) {
+      error_ = android::base::SystemErrorCodeToString(errno);
       file_.reset(nullptr);
       return false;
     }
@@ -96,22 +89,41 @@
     return true;
   }
 
+  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+    if (!StartEntry(path, flags)) {
+      return false;
+    }
+
+    const void* data = nullptr;
+    size_t len = 0;
+    while (in->Next(&data, &len)) {
+      if (!Write(data, static_cast<int>(len))) {
+        return false;
+      }
+    }
+    return !in->HadError();
+  }
+
+  bool HadError() const override { return !error_.empty(); }
+
+  std::string GetError() const override { return error_; }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(DirectoryWriter);
 
   std::string dir_;
   std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
+  std::string error_;
 };
 
 class ZipFileWriter : public IArchiveWriter {
  public:
   ZipFileWriter() = default;
 
-  bool Open(IDiagnostics* diag, const StringPiece& path) {
+  bool Open(const StringPiece& path) {
     file_ = {fopen(path.data(), "w+b"), fclose};
     if (!file_) {
-      diag->Error(DiagMessage() << "failed to Open " << path << ": "
-                                << strerror(errno));
+      error_ = android::base::SystemErrorCodeToString(errno);
       return false;
     }
     writer_ = util::make_unique<ZipWriter>(file_.get());
@@ -134,37 +146,83 @@
 
     int32_t result = writer_->StartEntry(path.data(), zip_flags);
     if (result != 0) {
+      error_ = ZipWriter::ErrorCodeString(result);
       return false;
     }
     return true;
   }
 
-  bool WriteEntry(const void* data, size_t len) override {
+  bool Write(const void* data, int len) override {
     int32_t result = writer_->WriteBytes(data, len);
     if (result != 0) {
+      error_ = ZipWriter::ErrorCodeString(result);
       return false;
     }
     return true;
   }
 
-  bool WriteEntry(const BigBuffer& buffer) override {
-    for (const BigBuffer::Block& b : buffer) {
-      int32_t result = writer_->WriteBytes(b.buffer.get(), b.size);
-      if (result != 0) {
-        return false;
-      }
-    }
-    return true;
-  }
-
   bool FinishEntry() override {
     int32_t result = writer_->FinishEntry();
     if (result != 0) {
+      error_ = ZipWriter::ErrorCodeString(result);
       return false;
     }
     return true;
   }
 
+  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+    while (true) {
+      if (!StartEntry(path, flags)) {
+        return false;
+      }
+
+      const void* data = nullptr;
+      size_t len = 0;
+      while (in->Next(&data, &len)) {
+        if (!Write(data, static_cast<int>(len))) {
+          return false;
+        }
+      }
+
+      if (in->HadError()) {
+        return false;
+      }
+
+      if (!FinishEntry()) {
+        return false;
+      }
+
+      // Check to see if the file was compressed enough. This is preserving behavior of AAPT.
+      if ((flags & ArchiveEntry::kCompress) != 0 && in->CanRewind()) {
+        ZipWriter::FileEntry last_entry;
+        int32_t result = writer_->GetLastEntry(&last_entry);
+        CHECK(result == 0);
+        if (last_entry.compressed_size + (last_entry.compressed_size / 10) >
+            last_entry.uncompressed_size) {
+          // The file was not compressed enough, rewind and store it uncompressed.
+          if (!in->Rewind()) {
+            // Well we tried, may as well keep what we had.
+            return true;
+          }
+
+          int32_t result = writer_->DiscardLastEntry();
+          if (result != 0) {
+            error_ = ZipWriter::ErrorCodeString(result);
+            return false;
+          }
+          flags &= ~ArchiveEntry::kCompress;
+
+          continue;
+        }
+      }
+      return true;
+    }
+  }
+
+  bool HadError() const override { return !error_.empty(); }
+
+  std::string GetError() const override { return error_; }
+
   virtual ~ZipFileWriter() {
     if (writer_) {
       writer_->Finish();
@@ -176,24 +234,26 @@
 
   std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
   std::unique_ptr<ZipWriter> writer_;
+  std::string error_;
 };
 
 }  // namespace
 
-std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(
-    IDiagnostics* diag, const StringPiece& path) {
-  std::unique_ptr<DirectoryWriter> writer =
-      util::make_unique<DirectoryWriter>();
-  if (!writer->Open(diag, path)) {
+std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
+                                                             const StringPiece& path) {
+  std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
+  if (!writer->Open(path)) {
+    diag->Error(DiagMessage(path) << writer->GetError());
     return {};
   }
   return std::move(writer);
 }
 
-std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(
-    IDiagnostics* diag, const StringPiece& path) {
+std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag,
+                                                           const StringPiece& path) {
   std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
-  if (!writer->Open(diag, path)) {
+  if (!writer->Open(path)) {
+    diag->Error(DiagMessage(path) << writer->GetError());
     return {};
   }
   return std::move(writer);
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index f0681bd..4ee4ce7 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -26,6 +26,7 @@
 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
 
 #include "Diagnostics.h"
+#include "io/Io.h"
 #include "util/BigBuffer.h"
 #include "util/Files.h"
 
@@ -42,19 +43,31 @@
   size_t uncompressed_size;
 };
 
-class IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
+class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream {
  public:
   virtual ~IArchiveWriter() = default;
 
+  virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0;
+
+  // Starts a new entry and allows caller to write bytes to it sequentially.
+  // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
+  // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
   virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
-  virtual bool WriteEntry(const BigBuffer& buffer) = 0;
-  virtual bool WriteEntry(const void* data, size_t len) = 0;
+
+  // Called to finish writing an entry previously started by StartEntry.
+  // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
   virtual bool FinishEntry() = 0;
 
-  // CopyingOutputStream implementations.
-  bool Write(const void* buffer, int size) override {
-    return WriteEntry(buffer, size);
-  }
+  // CopyingOutputStream implementation that allows sequential writes to this archive. Only
+  // valid between calls to StartEntry and FinishEntry.
+  virtual bool Write(const void* buffer, int size) = 0;
+
+  // Returns true if there was an error writing to the archive.
+  // The resulting error message can be retrieved from GetError().
+  virtual bool HadError() const = 0;
+
+  // Returns the error message if HadError() returns true.
+  virtual std::string GetError() const = 0;
 };
 
 std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
diff --git a/tools/aapt2/io/BigBufferInputStream.h b/tools/aapt2/io/BigBufferInputStream.h
new file mode 100644
index 0000000..92612c7
--- /dev/null
+++ b/tools/aapt2/io/BigBufferInputStream.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef AAPT_IO_BIGBUFFERINPUTSTREAM_H
+#define AAPT_IO_BIGBUFFERINPUTSTREAM_H
+
+#include "io/Io.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+namespace io {
+
+class BigBufferInputStream : public InputStream {
+ public:
+  inline explicit BigBufferInputStream(const BigBuffer* buffer)
+      : buffer_(buffer), iter_(buffer->begin()) {}
+  virtual ~BigBufferInputStream() = default;
+
+  bool Next(const void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  bool CanRewind() const override;
+
+  bool Rewind() override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
+
+  const BigBuffer* buffer_;
+  BigBuffer::const_iterator iter_;
+  size_t offset_ = 0;
+  size_t bytes_read_ = 0;
+};
+
+}  // namespace io
+}  // namespace aapt
+
+#endif  // AAPT_IO_BIGBUFFERINPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferOutputStream.h b/tools/aapt2/io/BigBufferOutputStream.h
new file mode 100644
index 0000000..95113bc
--- /dev/null
+++ b/tools/aapt2/io/BigBufferOutputStream.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
+#define AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
+
+#include "io/Io.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+namespace io {
+
+class BigBufferOutputStream : public OutputStream {
+ public:
+  inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
+  virtual ~BigBufferOutputStream() = default;
+
+  bool Next(void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+
+  BigBuffer* buffer_;
+};
+
+}  // namespace io
+}  // namespace aapt
+
+#endif  // AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferStreams.cpp b/tools/aapt2/io/BigBufferStreams.cpp
new file mode 100644
index 0000000..eb99033
--- /dev/null
+++ b/tools/aapt2/io/BigBufferStreams.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "io/BigBufferInputStream.h"
+#include "io/BigBufferOutputStream.h"
+
+namespace aapt {
+namespace io {
+
+//
+// BigBufferInputStream
+//
+
+bool BigBufferInputStream::Next(const void** data, size_t* size) {
+  if (iter_ == buffer_->end()) {
+    return false;
+  }
+
+  if (offset_ == iter_->size) {
+    ++iter_;
+    if (iter_ == buffer_->end()) {
+      return false;
+    }
+    offset_ = 0;
+  }
+
+  *data = iter_->buffer.get() + offset_;
+  *size = iter_->size - offset_;
+  bytes_read_ += iter_->size - offset_;
+  offset_ = iter_->size;
+  return true;
+}
+
+void BigBufferInputStream::BackUp(size_t count) {
+  if (count > offset_) {
+    bytes_read_ -= offset_;
+    offset_ = 0;
+  } else {
+    offset_ -= count;
+    bytes_read_ -= count;
+  }
+}
+
+bool BigBufferInputStream::CanRewind() const { return true; }
+
+bool BigBufferInputStream::Rewind() {
+  iter_ = buffer_->begin();
+  offset_ = 0;
+  bytes_read_ = 0;
+  return true;
+}
+
+size_t BigBufferInputStream::ByteCount() const { return bytes_read_; }
+
+bool BigBufferInputStream::HadError() const { return false; }
+
+//
+// BigBufferOutputStream
+//
+
+bool BigBufferOutputStream::Next(void** data, size_t* size) {
+  *data = buffer_->NextBlock(size);
+  return true;
+}
+
+void BigBufferOutputStream::BackUp(size_t count) { buffer_->BackUp(count); }
+
+size_t BigBufferOutputStream::ByteCount() const { return buffer_->size(); }
+
+bool BigBufferOutputStream::HadError() const { return false; }
+
+}  // namespace io
+}  // namespace aapt
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index fdc044d..09dc7ea 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -22,14 +22,13 @@
 #include "android-base/macros.h"
 #include "utils/FileMap.h"
 
+#include "io/Io.h"
+
 namespace aapt {
 namespace io {
 
-/**
- * Interface for a block of contiguous memory. An instance of this interface
- * owns the data.
- */
-class IData {
+// Interface for a block of contiguous memory. An instance of this interface owns the data.
+class IData : public InputStream {
  public:
   virtual ~IData() = default;
 
@@ -40,7 +39,8 @@
 class DataSegment : public IData {
  public:
   explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len)
-      : data_(std::move(data)), offset_(offset), len_(len) {}
+      : data_(std::move(data)), offset_(offset), len_(len), next_read_(offset) {}
+  virtual ~DataSegment() = default;
 
   const void* data() const override {
     return static_cast<const uint8_t*>(data_->data()) + offset_;
@@ -48,63 +48,163 @@
 
   size_t size() const override { return len_; }
 
+  bool Next(const void** data, size_t* size) override {
+    if (next_read_ == offset_ + len_) {
+      return false;
+    }
+    *data = static_cast<const uint8_t*>(data_->data()) + next_read_;
+    *size = len_ - (next_read_ - offset_);
+    next_read_ = offset_ + len_;
+    return true;
+  }
+
+  void BackUp(size_t count) override {
+    if (count > next_read_ - offset_) {
+      next_read_ = offset_;
+    } else {
+      next_read_ -= count;
+    }
+  }
+
+  bool CanRewind() const override { return true; }
+
+  bool Rewind() override {
+    next_read_ = offset_;
+    return true;
+  }
+
+  size_t ByteCount() const override { return next_read_ - offset_; }
+
+  bool HadError() const override { return false; }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(DataSegment);
 
   std::unique_ptr<IData> data_;
   size_t offset_;
   size_t len_;
+  size_t next_read_;
 };
 
-/**
- * Implementation of IData that exposes a memory mapped file. The mmapped file
- * is owned by this
- * object.
- */
+// Implementation of IData that exposes a memory mapped file.
+// The mmapped file is owned by this object.
 class MmappedData : public IData {
  public:
-  explicit MmappedData(android::FileMap&& map)
-      : map_(std::forward<android::FileMap>(map)) {}
+  explicit MmappedData(android::FileMap&& map) : map_(std::forward<android::FileMap>(map)) {}
+  virtual ~MmappedData() = default;
 
   const void* data() const override { return map_.getDataPtr(); }
 
   size_t size() const override { return map_.getDataLength(); }
 
+  bool Next(const void** data, size_t* size) override {
+    if (next_read_ == map_.getDataLength()) {
+      return false;
+    }
+    *data = reinterpret_cast<const uint8_t*>(map_.getDataPtr()) + next_read_;
+    *size = map_.getDataLength() - next_read_;
+    next_read_ = map_.getDataLength();
+    return true;
+  }
+
+  void BackUp(size_t count) override {
+    if (count > next_read_) {
+      next_read_ = 0;
+    } else {
+      next_read_ -= count;
+    }
+  }
+
+  bool CanRewind() const override { return true; }
+
+  bool Rewind() override {
+    next_read_ = 0;
+    return true;
+  }
+
+  size_t ByteCount() const override { return next_read_; }
+
+  bool HadError() const override { return false; }
+
  private:
+  DISALLOW_COPY_AND_ASSIGN(MmappedData);
+
   android::FileMap map_;
+  size_t next_read_ = 0;
 };
 
-/**
- * Implementation of IData that exposes a block of memory that was malloc'ed
- * (new'ed). The
- * memory is owned by this object.
- */
+// Implementation of IData that exposes a block of memory that was malloc'ed (new'ed).
+// The memory is owned by this object.
 class MallocData : public IData {
  public:
   MallocData(std::unique_ptr<const uint8_t[]> data, size_t size)
       : data_(std::move(data)), size_(size) {}
+  virtual ~MallocData() = default;
 
   const void* data() const override { return data_.get(); }
 
   size_t size() const override { return size_; }
 
+  bool Next(const void** data, size_t* size) override {
+    if (next_read_ == size_) {
+      return false;
+    }
+    *data = data_.get() + next_read_;
+    *size = size_ - next_read_;
+    next_read_ = size_;
+    return true;
+  }
+
+  void BackUp(size_t count) override {
+    if (count > next_read_) {
+      next_read_ = 0;
+    } else {
+      next_read_ -= count;
+    }
+  }
+
+  bool CanRewind() const override { return true; }
+
+  bool Rewind() override {
+    next_read_ = 0;
+    return true;
+  }
+
+  size_t ByteCount() const override { return next_read_; }
+
+  bool HadError() const override { return false; }
+
  private:
+  DISALLOW_COPY_AND_ASSIGN(MallocData);
+
   std::unique_ptr<const uint8_t[]> data_;
   size_t size_;
+  size_t next_read_ = 0;
 };
 
-/**
- * When mmap fails because the file has length 0, we use the EmptyData to
- * simulate data of length 0.
- */
+// When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0.
 class EmptyData : public IData {
  public:
+  virtual ~EmptyData() = default;
+
   const void* data() const override {
     static const uint8_t d = 0;
     return &d;
   }
 
   size_t size() const override { return 0u; }
+
+  bool Next(const void** /*data*/, size_t* /*size*/) override { return false; }
+
+  void BackUp(size_t /*count*/) override {}
+
+  bool CanRewind() const override { return true; }
+
+  bool Rewind() override { return true; }
+
+  size_t ByteCount() const override { return 0u; }
+
+  bool HadError() const override { return false; }
 };
 
 }  // namespace io
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 1ef9743..7ef6d88 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -30,40 +30,27 @@
 namespace aapt {
 namespace io {
 
-/**
- * Interface for a file, which could be a real file on the file system, or a
- * file inside
- * a ZIP archive.
- */
+// Interface for a file, which could be a real file on the file system, or a
+// file inside a ZIP archive.
 class IFile {
  public:
   virtual ~IFile() = default;
 
-  /**
-   * Open the file and return it as a block of contiguous memory. How this
-   * occurs is
-   * implementation dependent. For example, if this is a file on the file
-   * system, it may
-   * simply mmap the contents. If this file represents a compressed file in a
-   * ZIP archive,
-   * it may need to inflate it to memory, incurring a copy.
-   *
-   * Returns nullptr on failure.
-   */
+  // Open the file and return it as a block of contiguous memory. How this
+  // occurs is implementation dependent. For example, if this is a file on the file
+  // system, it may simply mmap the contents. If this file represents a compressed file in a
+  // ZIP archive, it may need to inflate it to memory, incurring a copy.
+  // Returns nullptr on failure.
   virtual std::unique_ptr<IData> OpenAsData() = 0;
 
-  /**
-   * Returns the source of this file. This is for presentation to the user and
-   * may not be a
-   * valid file system path (for example, it may contain a '@' sign to separate
-   * the files within
-   * a ZIP archive from the path to the containing ZIP archive.
-   */
+  // Returns the source of this file. This is for presentation to the user and
+  // may not be a valid file system path (for example, it may contain a '@' sign to separate
+  // the files within a ZIP archive from the path to the containing ZIP archive.
   virtual const Source& GetSource() const = 0;
 
   IFile* CreateFileSegment(size_t offset, size_t len);
 
-  /** Returns whether the file was compressed before it was stored in memory. */
+  // Returns whether the file was compressed before it was stored in memory.
   virtual bool WasCompressed() {
     return false;
   }
@@ -77,10 +64,7 @@
   std::list<std::unique_ptr<IFile>> segments_;
 };
 
-/**
- * An IFile that wraps an underlying IFile but limits it to a subsection of that
- * file.
- */
+// An IFile that wraps an underlying IFile but limits it to a subsection of that file.
 class FileSegment : public IFile {
  public:
   explicit FileSegment(IFile* file, size_t offset, size_t len)
@@ -106,11 +90,8 @@
   virtual IFile* Next() = 0;
 };
 
-/**
- * Interface for a collection of files, all of which share a common source. That
- * source may
- * simply be the filesystem, or a ZIP archive.
- */
+// Interface for a collection of files, all of which share a common source. That source may
+// simply be the filesystem, or a ZIP archive.
 class IFileCollection {
  public:
   virtual ~IFileCollection() = default;
diff --git a/tools/aapt2/io/Io.cpp b/tools/aapt2/io/Io.cpp
index cab4b65..f5c5737 100644
--- a/tools/aapt2/io/Io.cpp
+++ b/tools/aapt2/io/Io.cpp
@@ -16,7 +16,6 @@
 
 #include "io/Io.h"
 
-#include <algorithm>
 #include <cstring>
 
 namespace aapt {
@@ -24,15 +23,15 @@
 
 bool Copy(OutputStream* out, InputStream* in) {
   const void* in_buffer;
-  int in_len;
+  size_t in_len;
   while (in->Next(&in_buffer, &in_len)) {
     void* out_buffer;
-    int out_len;
+    size_t out_len;
     if (!out->Next(&out_buffer, &out_len)) {
       return !out->HadError();
     }
 
-    const int bytes_to_copy = std::min(in_len, out_len);
+    const size_t bytes_to_copy = in_len < out_len ? in_len : out_len;
     memcpy(out_buffer, in_buffer, bytes_to_copy);
     out->BackUp(out_len - bytes_to_copy);
     in->BackUp(in_len - bytes_to_copy);
diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h
index 33cdc7b..2a34d4d 100644
--- a/tools/aapt2/io/Io.h
+++ b/tools/aapt2/io/Io.h
@@ -19,42 +19,76 @@
 
 #include <string>
 
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
 namespace aapt {
 namespace io {
 
-/**
- * InputStream interface that inherits from protobuf's ZeroCopyInputStream,
- * but adds error handling methods to better report issues.
- *
- * The code style here matches the protobuf style.
- */
-class InputStream : public ::google::protobuf::io::ZeroCopyInputStream {
+// InputStream interface that mimics protobuf's ZeroCopyInputStream,
+// with added error handling methods to better report issues.
+class InputStream {
  public:
+  virtual ~InputStream() = default;
+
+  // Returns a chunk of data for reading. data and size must not be nullptr.
+  // Returns true so long as there is more data to read, returns false if an error occurred
+  // or no data remains. If an error occurred, check HadError().
+  // The stream owns the buffer returned from this method and the buffer is invalidated
+  // anytime another mutable method is called.
+  virtual bool Next(const void** data, size_t* size) = 0;
+
+  // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+  // from Next().
+  // Useful when the last block returned from Next() wasn't fully read.
+  virtual void BackUp(size_t count) = 0;
+
+  // Returns true if this InputStream can rewind. If so, Rewind() can be called.
+  virtual bool CanRewind() const { return false; };
+
+  // Rewinds the stream to the beginning so it can be read again.
+  // Returns true if the rewind succeeded.
+  // This does nothing if CanRewind() returns false.
+  virtual bool Rewind() { return false; }
+
+  // Returns the number of bytes that have been read from the stream.
+  virtual size_t ByteCount() const = 0;
+
+  // Returns an error message if HadError() returned true.
   virtual std::string GetError() const { return {}; }
 
+  // Returns true if an error occurred. Errors are permanent.
   virtual bool HadError() const = 0;
 };
 
-/**
- * OutputStream interface that inherits from protobuf's ZeroCopyOutputStream,
- * but adds error handling methods to better report issues.
- *
- * The code style here matches the protobuf style.
- */
-class OutputStream : public ::google::protobuf::io::ZeroCopyOutputStream {
+// OutputStream interface that mimics protobuf's ZeroCopyOutputStream,
+// with added error handling methods to better report issues.
+class OutputStream {
  public:
+  virtual ~OutputStream() = default;
+
+  // Returns a buffer to which data can be written to. The data written to this buffer will
+  // eventually be written to the stream. Call BackUp() if the data written doesn't occupy the
+  // entire buffer.
+  // Return false if there was an error.
+  // The stream owns the buffer returned from this method and the buffer is invalidated
+  // anytime another mutable method is called.
+  virtual bool Next(void** data, size_t* size) = 0;
+
+  // Backup count bytes, where count is smaller or equal to the size of the last buffer returned
+  // from Next().
+  // Useful for when the last block returned from Next() wasn't fully written to.
+  virtual void BackUp(size_t count) = 0;
+
+  // Returns the number of bytes that have been written to the stream.
+  virtual size_t ByteCount() const = 0;
+
+  // Returns an error message if HadError() returned true.
   virtual std::string GetError() const { return {}; }
 
+  // Returns true if an error occurred. Errors are permanent.
   virtual bool HadError() const = 0;
 };
 
-/**
- * Copies the data from in to out. Returns true if there was no error.
- * If there was an error, check the individual streams' HadError/GetError
- * methods.
- */
+// Copies the data from in to out. Returns false if there was an error.
+// If there was an error, check the individual streams' HadError/GetError methods.
 bool Copy(OutputStream* out, InputStream* in);
 
 }  // namespace io
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 1b4d5bb..7f71589 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -38,6 +38,7 @@
 #include "flatten/Archive.h"
 #include "flatten/TableFlattener.h"
 #include "flatten/XmlFlattener.h"
+#include "io/BigBufferInputStream.h"
 #include "io/FileSystem.h"
 #include "io/ZipArchive.h"
 #include "java/JavaClassGenerator.h"
@@ -168,34 +169,57 @@
   int min_sdk_version_ = 0;
 };
 
+static bool CopyInputStreamToArchive(io::InputStream* in, const std::string& out_path,
+                                     uint32_t compression_flags, IArchiveWriter* writer,
+                                     IAaptContext* context) {
+  if (context->IsVerbose()) {
+    context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive");
+  }
+
+  if (!writer->WriteFile(out_path, compression_flags, in)) {
+    context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path
+                                                   << " to archive: " << writer->GetError());
+    return false;
+  }
+  return true;
+}
+
 static bool CopyFileToArchive(io::IFile* file, const std::string& out_path,
                               uint32_t compression_flags,
                               IArchiveWriter* writer, IAaptContext* context) {
   std::unique_ptr<io::IData> data = file->OpenAsData();
   if (!data) {
-    context->GetDiagnostics()->Error(DiagMessage(file->GetSource())
-                                     << "failed to open file");
+    context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file");
     return false;
   }
+  return CopyInputStreamToArchive(data.get(), out_path, compression_flags, writer, context);
+}
 
-  const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
-  const size_t buffer_size = data->size();
-
+static bool CopyProtoToArchive(::google::protobuf::MessageLite* proto_msg,
+                               const std::string& out_path, uint32_t compression_flags,
+                               IArchiveWriter* writer, IAaptContext* context) {
   if (context->IsVerbose()) {
-    context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path
-                                                  << " to archive");
+    context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive");
   }
 
   if (writer->StartEntry(out_path, compression_flags)) {
-    if (writer->WriteEntry(buffer, buffer_size)) {
-      if (writer->FinishEntry()) {
-        return true;
+    // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
+    {
+      // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
+      ::google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+      if (!proto_msg->SerializeToZeroCopyStream(&adaptor)) {
+        context->GetDiagnostics()->Error(DiagMessage()
+                                         << "failed to write " << out_path << " to archive");
+        return false;
       }
     }
-  }
 
-  context->GetDiagnostics()->Error(DiagMessage() << "failed to write file "
-                                                 << out_path);
+    if (writer->FinishEntry()) {
+      return true;
+    }
+  }
+  context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path
+                                                 << " to archive: " << writer->GetError());
   return false;
 }
 
@@ -221,16 +245,9 @@
     context->GetDiagnostics()->Note(msg);
   }
 
-  if (writer->StartEntry(path, ArchiveEntry::kCompress)) {
-    if (writer->WriteEntry(buffer)) {
-      if (writer->FinishEntry()) {
-        return true;
-      }
-    }
-  }
-  context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << path
-                                                 << " to archive");
-  return false;
+  io::BigBufferInputStream input_stream(&buffer);
+  return CopyInputStreamToArchive(&input_stream, path.to_string(), ArchiveEntry::kCompress, writer,
+                                  context);
 }
 
 static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source,
@@ -243,8 +260,7 @@
     return {};
   }
 
-  std::unique_ptr<ResourceTable> table =
-      DeserializeTableFromPb(pb_table, source, diag);
+  std::unique_ptr<ResourceTable> table = DeserializeTableFromPb(pb_table, source, diag);
   if (!table) {
     return {};
   }
@@ -898,49 +914,18 @@
     BigBuffer buffer(1024);
     TableFlattener flattener(options_.table_flattener_options, &buffer);
     if (!flattener.Consume(context_, table)) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table");
       return false;
     }
 
-    if (writer->StartEntry("resources.arsc", ArchiveEntry::kAlign)) {
-      if (writer->WriteEntry(buffer)) {
-        if (writer->FinishEntry()) {
-          return true;
-        }
-      }
-    }
-
-    context_->GetDiagnostics()->Error(
-        DiagMessage() << "failed to write resources.arsc to archive");
-    return false;
+    io::BigBufferInputStream input_stream(&buffer);
+    return CopyInputStreamToArchive(&input_stream, "resources.arsc", ArchiveEntry::kAlign, writer,
+                                    context_);
   }
 
   bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
-    // Create the file/zip entry.
-    if (!writer->StartEntry("resources.arsc.flat", 0)) {
-      context_->GetDiagnostics()->Error(DiagMessage() << "failed to open");
-      return false;
-    }
-
-    // Make sure CopyingOutputStreamAdaptor is deleted before we call
-    // writer->FinishEntry().
-    {
-      // Wrap our IArchiveWriter with an adaptor that implements the
-      // ZeroCopyOutputStream interface.
-      CopyingOutputStreamAdaptor adaptor(writer);
-
-      std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table);
-      if (!pb_table->SerializeToZeroCopyStream(&adaptor)) {
-        context_->GetDiagnostics()->Error(DiagMessage() << "failed to write");
-        return false;
-      }
-    }
-
-    if (!writer->FinishEntry()) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed to finish entry");
-      return false;
-    }
-    return true;
+    std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table);
+    return CopyProtoToArchive(pb_table.get(), "resources.arsc.flat", 0, writer, context_);
   }
 
   bool WriteJavaFile(ResourceTable* table,
@@ -971,8 +956,7 @@
 
     JavaClassGenerator generator(context_, table, java_options);
     if (!generator.Generate(package_name_to_generate, out_package, &fout)) {
-      context_->GetDiagnostics()->Error(DiagMessage(out_path)
-                                        << generator.getError());
+      context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.getError());
       return false;
     }
 
@@ -1484,7 +1468,6 @@
 
     if (options_.package_type == PackageType::kStaticLib) {
       if (!FlattenTableToPb(table, writer)) {
-        context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc.flat");
         return false;
       }
     } else {
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 1c9a75d..9899f80 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -1,5 +1,12 @@
 # Android Asset Packaging Tool 2.0 (AAPT2) release notes
 
+## Version 2.11
+### `aapt2 link ...`
+- Adds the ability to specify assets directories with the -A parameter. Assets work just like
+  assets in the original AAPT. It is not recommended to package assets with aapt2, however,
+  since the resulting APK is post-processed by other tools anyways. Assets do not get processed
+  by AAPT2, just copied, so incremental building gets slower if they are included early on.
+
 ## Version 2.10
 ### `aapt2 link ...`
 - Add ability to specify package ID to compile with for regular apps (not shared or static libs).
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index 43f4ebc..e4b2020 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -127,6 +127,9 @@
 
     private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX,
             float lightPosY, float lightHeight, float lightSize, Canvas canvas) {
+        if (canvas == null || canvas.getWidth() == 0 || canvas.getHeight() == 0) {
+            return;
+        }
 
         // The polygon of shadow (same as the original item)
         float[] shadowPoly = new float[coordinates.length * 3];
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
index b2baa98..736b287 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
Binary files differ
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index af48d0a..10ffd8a 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -139,7 +139,7 @@
 
     void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable);
 
-    String getWpsNfcConfigurationToken(int netId);
+    String getCurrentNetworkWpsNfcConfigurationToken();
 
     void enableVerboseLogging(int verbose);
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 824c436..ae6a679 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1437,15 +1437,15 @@
     }
 
     /**
-     * Creates a configuration token describing the network referenced by {@code netId}
-     * of MIME type application/vnd.wfa.wsc. Can be used to configure WiFi networks via NFC.
+     * Creates a configuration token describing the current network of MIME type
+     * application/vnd.wfa.wsc. Can be used to configure WiFi networks via NFC.
      *
-     * @return hex-string encoded configuration token
+     * @return hex-string encoded configuration token or null if there is no current network
      * @hide
      */
-    public String getWpsNfcConfigurationToken(int netId) {
+    public String getCurrentNetworkWpsNfcConfigurationToken() {
         try {
-            return mService.getWpsNfcConfigurationToken(netId);
+            return mService.getCurrentNetworkWpsNfcConfigurationToken();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }