Merge changes Ia971b2bc,I0001eb3d,I55a6951e

* changes:
  Remove framework-protos from SystemUI
  Disable proguard for PluginDummyLib
  Enable multidex for dx
diff --git a/Android.mk b/Android.mk
index e324a75..004e352 100644
--- a/Android.mk
+++ b/Android.mk
@@ -140,8 +140,8 @@
 	core/java/android/bluetooth/IBluetoothInputHost.aidl \
 	core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl \
 	core/java/android/bluetooth/IBluetoothGatt.aidl \
-	core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl \
-	core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl \
+	core/java/android/bluetooth/IBluetoothGattCallback.aidl \
+	core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \
 	core/java/android/bluetooth/le/IAdvertiserCallback.aidl \
 	core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl \
 	core/java/android/bluetooth/le/IPeriodicAdvertisingCallback.aidl \
@@ -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 5018ecf..b58f706 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
@@ -14463,7 +14464,7 @@
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void unlock();
-    field public static final deprecated java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+    field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
     field public static final deprecated java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
     field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2
     field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64
@@ -21817,23 +21818,23 @@
   }
 
   public final class MediaCas {
-    ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+    ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
     method public void closeSession(byte[]);
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method public static boolean isSystemIdSupported(int);
-    method public byte[] openSession(int);
-    method public byte[] openSession(int, int);
-    method public void processEcm(byte[], byte[], int, int);
-    method public void processEcm(byte[], byte[]);
-    method public void processEmm(byte[], int, int);
-    method public void processEmm(byte[]);
-    method public void provision(java.lang.String);
-    method public void refreshEntitlements(int, byte[]);
+    method public byte[] openSession(int) throws android.media.MediaCasException;
+    method public byte[] openSession(int, int) throws android.media.MediaCasException;
+    method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+    method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+    method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+    method public void processEmm(byte[]) throws android.media.MediaCasException;
+    method public void provision(java.lang.String) throws android.media.MediaCasException;
+    method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
     method public void release();
-    method public void sendEvent(int, int, byte[]);
+    method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
     method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
-    method public void setPrivateData(byte[]);
-    method public void setSessionPrivateData(byte[], byte[]);
+    method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+    method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
   }
 
   public static abstract interface MediaCas.EventListener {
@@ -21846,7 +21847,22 @@
   }
 
   public class MediaCasException extends java.lang.Exception {
-    ctor public MediaCasException(java.lang.String);
+  }
+
+  public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+  }
+
+  public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+  }
+
+  public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+  }
+
+  public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+  }
+
+  public class MediaCasStateException extends java.lang.IllegalStateException {
+    method public java.lang.String getDiagnosticInfo();
   }
 
   public final class MediaCodec {
@@ -22274,7 +22290,7 @@
   }
 
   public final class MediaDescrambler {
-    ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+    ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
     method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
     method public final void release();
     method public final boolean requiresSecureDecoderComponent(java.lang.String);
@@ -23585,10 +23601,6 @@
     field public static final int TONE_SUP_RINGTONE = 23; // 0x17
   }
 
-  public final class UnsupportedCasException extends android.media.MediaCasException {
-    ctor public UnsupportedCasException(java.lang.String);
-  }
-
   public final class UnsupportedSchemeException extends android.media.MediaDrmException {
     ctor public UnsupportedSchemeException(java.lang.String);
   }
@@ -24325,9 +24337,9 @@
     method public void setRepeatMode(int);
     method public void setSessionActivity(android.app.PendingIntent);
     method public void setShuffleModeEnabled(boolean);
-    field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+    field public static final deprecated int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
     field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
-    field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+    field public static final deprecated int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
   }
 
   public static abstract class MediaSession.Callback {
@@ -34249,6 +34261,7 @@
     method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
     method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static void ejectRoot(android.content.ContentResolver, android.net.Uri);
     method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
     method public static java.lang.String getDocumentId(android.net.Uri);
     method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -34317,6 +34330,7 @@
     field public static final java.lang.String COLUMN_TITLE = "title";
     field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
     field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20
     field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
     field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
     field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
@@ -34330,6 +34344,7 @@
     method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
+    method public void ejectRoot(java.lang.String);
     method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String);
     method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
@@ -39933,13 +39948,17 @@
     method public int getDataActivity();
     method public int getDataNetworkType();
     method public int getDataState();
-    method public java.lang.String getDeviceId();
-    method public java.lang.String getDeviceId(int);
+    method public deprecated java.lang.String getDeviceId();
+    method public deprecated java.lang.String getDeviceId(int);
     method public java.lang.String getDeviceSoftwareVersion();
     method public java.lang.String[] getForbiddenPlmns();
     method public java.lang.String getGroupIdLevel1();
     method public java.lang.String getIccAuthentication(int, int, java.lang.String);
+    method public java.lang.String getImei();
+    method public java.lang.String getImei(int);
     method public java.lang.String getLine1Number();
+    method public java.lang.String getMeid();
+    method public java.lang.String getMeid(int);
     method public java.lang.String getMmsUAProfUrl();
     method public java.lang.String getMmsUserAgent();
     method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
@@ -43228,7 +43247,6 @@
     method public E get(long, E);
     method public int indexOfKey(long);
     method public int indexOfValue(E);
-    method public int indexOfValueByValue(E);
     method public long keyAt(int);
     method public void put(long, E);
     method public void remove(long);
@@ -43431,7 +43449,6 @@
     method public E get(int, E);
     method public int indexOfKey(int);
     method public int indexOfValue(E);
-    method public int indexOfValueByValue(E);
     method public int keyAt(int);
     method public void put(int, E);
     method public void remove(int);
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 2bf01d1..9e1c123 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -93,6 +93,7 @@
     field public static final java.lang.String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
     field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
     field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
+    field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
     field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
     field public static final java.lang.String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
     field public static final java.lang.String CONTROL_VPN = "android.permission.CONTROL_VPN";
@@ -992,6 +993,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
@@ -15188,7 +15190,7 @@
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void unlock();
-    field public static final deprecated java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+    field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
     field public static final deprecated java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
     field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2
     field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64
@@ -23603,23 +23605,23 @@
   }
 
   public final class MediaCas {
-    ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+    ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
     method public void closeSession(byte[]);
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method public static boolean isSystemIdSupported(int);
-    method public byte[] openSession(int);
-    method public byte[] openSession(int, int);
-    method public void processEcm(byte[], byte[], int, int);
-    method public void processEcm(byte[], byte[]);
-    method public void processEmm(byte[], int, int);
-    method public void processEmm(byte[]);
-    method public void provision(java.lang.String);
-    method public void refreshEntitlements(int, byte[]);
+    method public byte[] openSession(int) throws android.media.MediaCasException;
+    method public byte[] openSession(int, int) throws android.media.MediaCasException;
+    method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+    method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+    method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+    method public void processEmm(byte[]) throws android.media.MediaCasException;
+    method public void provision(java.lang.String) throws android.media.MediaCasException;
+    method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
     method public void release();
-    method public void sendEvent(int, int, byte[]);
+    method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
     method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
-    method public void setPrivateData(byte[]);
-    method public void setSessionPrivateData(byte[], byte[]);
+    method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+    method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
   }
 
   public static abstract interface MediaCas.EventListener {
@@ -23632,7 +23634,22 @@
   }
 
   public class MediaCasException extends java.lang.Exception {
-    ctor public MediaCasException(java.lang.String);
+  }
+
+  public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+  }
+
+  public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+  }
+
+  public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+  }
+
+  public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+  }
+
+  public class MediaCasStateException extends java.lang.IllegalStateException {
+    method public java.lang.String getDiagnosticInfo();
   }
 
   public final class MediaCodec {
@@ -24060,7 +24077,7 @@
   }
 
   public final class MediaDescrambler {
-    ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+    ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
     method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
     method public final void release();
     method public final boolean requiresSecureDecoderComponent(java.lang.String);
@@ -25382,10 +25399,6 @@
     field public static final int TONE_SUP_RINGTONE = 23; // 0x17
   }
 
-  public final class UnsupportedCasException extends android.media.MediaCasException {
-    ctor public UnsupportedCasException(java.lang.String);
-  }
-
   public final class UnsupportedSchemeException extends android.media.MediaDrmException {
     ctor public UnsupportedSchemeException(java.lang.String);
   }
@@ -26194,9 +26207,9 @@
     method public void setRepeatMode(int);
     method public void setSessionActivity(android.app.PendingIntent);
     method public void setShuffleModeEnabled(boolean);
-    field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+    field public static final deprecated int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
     field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
-    field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+    field public static final deprecated int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
   }
 
   public static abstract class MediaSession.Callback {
@@ -37149,6 +37162,7 @@
     method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
     method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static void ejectRoot(android.content.ContentResolver, android.net.Uri);
     method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
     method public static java.lang.String getDocumentId(android.net.Uri);
     method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -37217,6 +37231,7 @@
     field public static final java.lang.String COLUMN_TITLE = "title";
     field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
     field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20
     field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
     field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
     field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
@@ -37230,6 +37245,7 @@
     method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
+    method public void ejectRoot(java.lang.String);
     method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String);
     method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
@@ -43329,8 +43345,8 @@
     method public deprecated boolean getDataEnabled(int);
     method public int getDataNetworkType();
     method public int getDataState();
-    method public java.lang.String getDeviceId();
-    method public java.lang.String getDeviceId(int);
+    method public deprecated java.lang.String getDeviceId();
+    method public deprecated java.lang.String getDeviceId(int);
     method public java.lang.String getDeviceSoftwareVersion();
     method public java.lang.String[] getForbiddenPlmns();
     method public java.lang.String getGroupIdLevel1();
@@ -43338,6 +43354,8 @@
     method public java.lang.String getImei();
     method public java.lang.String getImei(int);
     method public java.lang.String getLine1Number();
+    method public java.lang.String getMeid();
+    method public java.lang.String getMeid(int);
     method public java.lang.String getMmsUAProfUrl();
     method public java.lang.String getMmsUserAgent();
     method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
@@ -46685,7 +46703,6 @@
     method public E get(long, E);
     method public int indexOfKey(long);
     method public int indexOfValue(E);
-    method public int indexOfValueByValue(E);
     method public long keyAt(int);
     method public void put(long, E);
     method public void remove(long);
@@ -46888,7 +46905,6 @@
     method public E get(int, E);
     method public int indexOfKey(int);
     method public int indexOfValue(E);
-    method public int indexOfValueByValue(E);
     method public int keyAt(int);
     method public void put(int, E);
     method public void remove(int);
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 da25aa9..67d4586 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
@@ -14512,7 +14513,7 @@
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void takePicture(android.hardware.Camera.ShutterCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback, android.hardware.Camera.PictureCallback);
     method public final void unlock();
-    field public static final deprecated java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+    field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
     field public static final deprecated java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
     field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2
     field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64
@@ -21928,23 +21929,23 @@
   }
 
   public final class MediaCas {
-    ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+    ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
     method public void closeSession(byte[]);
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method public static boolean isSystemIdSupported(int);
-    method public byte[] openSession(int);
-    method public byte[] openSession(int, int);
-    method public void processEcm(byte[], byte[], int, int);
-    method public void processEcm(byte[], byte[]);
-    method public void processEmm(byte[], int, int);
-    method public void processEmm(byte[]);
-    method public void provision(java.lang.String);
-    method public void refreshEntitlements(int, byte[]);
+    method public byte[] openSession(int) throws android.media.MediaCasException;
+    method public byte[] openSession(int, int) throws android.media.MediaCasException;
+    method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+    method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+    method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+    method public void processEmm(byte[]) throws android.media.MediaCasException;
+    method public void provision(java.lang.String) throws android.media.MediaCasException;
+    method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
     method public void release();
-    method public void sendEvent(int, int, byte[]);
+    method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
     method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
-    method public void setPrivateData(byte[]);
-    method public void setSessionPrivateData(byte[], byte[]);
+    method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+    method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
   }
 
   public static abstract interface MediaCas.EventListener {
@@ -21957,7 +21958,22 @@
   }
 
   public class MediaCasException extends java.lang.Exception {
-    ctor public MediaCasException(java.lang.String);
+  }
+
+  public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+  }
+
+  public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+  }
+
+  public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+  }
+
+  public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+  }
+
+  public class MediaCasStateException extends java.lang.IllegalStateException {
+    method public java.lang.String getDiagnosticInfo();
   }
 
   public final class MediaCodec {
@@ -22385,7 +22401,7 @@
   }
 
   public final class MediaDescrambler {
-    ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+    ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
     method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
     method public final void release();
     method public final boolean requiresSecureDecoderComponent(java.lang.String);
@@ -23696,10 +23712,6 @@
     field public static final int TONE_SUP_RINGTONE = 23; // 0x17
   }
 
-  public final class UnsupportedCasException extends android.media.MediaCasException {
-    ctor public UnsupportedCasException(java.lang.String);
-  }
-
   public final class UnsupportedSchemeException extends android.media.MediaDrmException {
     ctor public UnsupportedSchemeException(java.lang.String);
   }
@@ -24436,9 +24448,9 @@
     method public void setRepeatMode(int);
     method public void setSessionActivity(android.app.PendingIntent);
     method public void setShuffleModeEnabled(boolean);
-    field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+    field public static final deprecated int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
     field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
-    field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+    field public static final deprecated int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
   }
 
   public static abstract class MediaSession.Callback {
@@ -34387,6 +34399,7 @@
     method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException;
     method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
+    method public static void ejectRoot(android.content.ContentResolver, android.net.Uri);
     method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
     method public static java.lang.String getDocumentId(android.net.Uri);
     method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
@@ -34455,6 +34468,7 @@
     field public static final java.lang.String COLUMN_TITLE = "title";
     field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
     field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20
     field public static final int FLAG_SUPPORTS_IS_CHILD = 16; // 0x10
     field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
     field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
@@ -34468,6 +34482,7 @@
     method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
+    method public void ejectRoot(java.lang.String);
     method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String);
     method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
@@ -40134,13 +40149,17 @@
     method public int getDataActivity();
     method public int getDataNetworkType();
     method public int getDataState();
-    method public java.lang.String getDeviceId();
-    method public java.lang.String getDeviceId(int);
+    method public deprecated java.lang.String getDeviceId();
+    method public deprecated java.lang.String getDeviceId(int);
     method public java.lang.String getDeviceSoftwareVersion();
     method public java.lang.String[] getForbiddenPlmns();
     method public java.lang.String getGroupIdLevel1();
     method public java.lang.String getIccAuthentication(int, int, java.lang.String);
+    method public java.lang.String getImei();
+    method public java.lang.String getImei(int);
     method public java.lang.String getLine1Number();
+    method public java.lang.String getMeid();
+    method public java.lang.String getMeid(int);
     method public java.lang.String getMmsUAProfUrl();
     method public java.lang.String getMmsUserAgent();
     method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
@@ -43434,7 +43453,6 @@
     method public E get(long, E);
     method public int indexOfKey(long);
     method public int indexOfValue(E);
-    method public int indexOfValueByValue(E);
     method public long keyAt(int);
     method public void put(long, E);
     method public void remove(long);
@@ -43637,7 +43655,6 @@
     method public E get(int, E);
     method public int indexOfKey(int);
     method public int indexOfValue(E);
-    method public int indexOfValueByValue(E);
     method public int keyAt(int);
     method public void put(int, E);
     method public void remove(int);
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/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 0d859a1..d710d8b 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -552,6 +552,7 @@
     int[] mAdded;
     BackStackState[] mBackStack;
     int mPrimaryNavActiveIndex = -1;
+    int mNextFragmentIndex;
     
     public FragmentManagerState() {
     }
@@ -561,6 +562,7 @@
         mAdded = in.createIntArray();
         mBackStack = in.createTypedArray(BackStackState.CREATOR);
         mPrimaryNavActiveIndex = in.readInt();
+        mNextFragmentIndex = in.readInt();
     }
     
     public int describeContents() {
@@ -572,6 +574,7 @@
         dest.writeIntArray(mAdded);
         dest.writeTypedArray(mBackStack, flags);
         dest.writeInt(mPrimaryNavActiveIndex);
+        dest.writeInt(mNextFragmentIndex);
     }
     
     public static final Parcelable.Creator<FragmentManagerState> CREATOR
@@ -638,10 +641,10 @@
 
     ArrayList<OpGenerator> mPendingActions;
     boolean mExecutingActions;
-    
-    ArrayList<Fragment> mActive;
+
+    int mNextFragmentIndex = 0;
+    SparseArray<Fragment> mActive;
     ArrayList<Fragment> mAdded;
-    ArrayList<Integer> mAvailIndices;
     ArrayList<BackStackRecord> mBackStack;
     ArrayList<Fragment> mCreatedMenus;
     
@@ -839,6 +842,7 @@
         }
 
         doPendingDeferredStart();
+        burpActive();
         return executePop;
     }
 
@@ -882,10 +886,6 @@
         if (index == -1) {
             return null;
         }
-        if (index >= mActive.size()) {
-            throwException(new IllegalStateException("Fragment no longer exists for key "
-                    + key + ": index " + index));
-        }
         Fragment f = mActive.get(index);
         if (f == null) {
             throwException(new IllegalStateException("Fragment no longer exists for key "
@@ -939,7 +939,7 @@
                         writer.print(Integer.toHexString(System.identityHashCode(this)));
                         writer.println(":");
                 for (int i=0; i<N; i++) {
-                    Fragment f = mActive.get(i);
+                    Fragment f = mActive.valueAt(i);
                     writer.print(prefix); writer.print("  #"); writer.print(i);
                             writer.print(": "); writer.println(f);
                     if (f != null) {
@@ -1034,10 +1034,6 @@
             writer.print(prefix); writer.print("  mNoTransactionsBecause=");
                     writer.println(mNoTransactionsBecause);
         }
-        if (mAvailIndices != null && mAvailIndices.size() > 0) {
-            writer.print(prefix); writer.print("  mAvailIndices: ");
-                    writer.println(Arrays.toString(mAvailIndices.toArray()));
-        }
     }
 
     Animator loadAnimator(Fragment fragment, int transit, boolean enter,
@@ -1164,7 +1160,7 @@
                         // If we have a target fragment, push it along to at least CREATED
                         // so that this one can rely on it as an initialized dependency.
                         if (f.mTarget != null) {
-                            if (!mActive.contains(f.mTarget)) {
+                            if (mActive.get(f.mTarget.mIndex) != f.mTarget) {
                                 throw new IllegalStateException("Fragment " + f
                                         + " declared target fragment " + f.mTarget
                                         + " that does not belong to this FragmentManager!");
@@ -1557,7 +1553,7 @@
             // and detached.
             final int numActive = mActive.size();
             for (int i = 0; i < numActive; i++) {
-                Fragment f = mActive.get(i);
+                Fragment f = mActive.valueAt(i);
                 if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {
                     moveFragmentToExpectedState(f);
                     if (f.mLoaderManager != null) {
@@ -1581,7 +1577,7 @@
         if (mActive == null) return;
 
         for (int i=0; i<mActive.size(); i++) {
-            Fragment f = mActive.get(i);
+            Fragment f = mActive.valueAt(i);
             if (f != null) {
                 performPendingDeferredStart(f);
             }
@@ -1592,18 +1588,12 @@
         if (f.mIndex >= 0) {
             return;
         }
-        
-        if (mAvailIndices == null || mAvailIndices.size() <= 0) {
-            if (mActive == null) {
-                mActive = new ArrayList<Fragment>();
-            }
-            f.setIndex(mActive.size(), mParent);
-            mActive.add(f);
-            
-        } else {
-            f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
-            mActive.set(f.mIndex, f);
+
+        f.setIndex(mNextFragmentIndex++, mParent);
+        if (mActive == null) {
+            mActive = new SparseArray<>();
         }
+        mActive.put(f.mIndex, f);
         if (DEBUG) Log.v(TAG, "Allocated fragment index " + f);
     }
     
@@ -1613,11 +1603,9 @@
         }
         
         if (DEBUG) Log.v(TAG, "Freeing fragment index " + f);
-        mActive.set(f.mIndex, null);
-        if (mAvailIndices == null) {
-            mAvailIndices = new ArrayList<Integer>();
-        }
-        mAvailIndices.add(f.mIndex);
+        // Don't remove yet. That happens in burpActive(). This prevents
+        // concurrent modification while iterating over mActive
+        mActive.put(f.mIndex, null);
         mHost.inactivateFragment(f.mWho);
         f.initState();
     }
@@ -1754,7 +1742,7 @@
         if (mActive != null) {
             // Now for any known fragment.
             for (int i=mActive.size()-1; i>=0; i--) {
-                Fragment f = mActive.get(i);
+                Fragment f = mActive.valueAt(i);
                 if (f != null && f.mFragmentId == id) {
                     return f;
                 }
@@ -1776,7 +1764,7 @@
         if (mActive != null && tag != null) {
             // Now for any known fragment.
             for (int i=mActive.size()-1; i>=0; i--) {
-                Fragment f = mActive.get(i);
+                Fragment f = mActive.valueAt(i);
                 if (f != null && tag.equals(f.mTag)) {
                     return f;
                 }
@@ -1788,7 +1776,7 @@
     public Fragment findFragmentByWho(String who) {
         if (mActive != null && who != null) {
             for (int i=mActive.size()-1; i>=0; i--) {
-                Fragment f = mActive.get(i);
+                Fragment f = mActive.valueAt(i);
                 if (f != null && (f=f.findFragmentByWho(who)) != null) {
                     return f;
                 }
@@ -1953,6 +1941,7 @@
         }
 
         doPendingDeferredStart();
+        burpActive();
     }
 
     /**
@@ -1983,6 +1972,7 @@
         }
 
         doPendingDeferredStart();
+        burpActive();
 
         return didSomething;
     }
@@ -2248,7 +2238,7 @@
             for (int i = 0; i < numActive; i++) {
                 // Allow added fragments to be removed during the pop since we aren't going
                 // to move them to the final state with moveToState(mCurState).
-                Fragment fragment = mActive.get(i);
+                Fragment fragment = mActive.valueAt(i);
                 if (fragment != null && fragment.mView != null && fragment.mIsNewlyAdded
                         && record.interactsWith(fragment.mContainerId)) {
                     fragment.mIsNewlyAdded = false;
@@ -2360,7 +2350,7 @@
     private void endAnimatingAwayFragments() {
         final int numFragments = mActive == null ? 0 : mActive.size();
         for (int i = 0; i < numFragments; i++) {
-            Fragment fragment = mActive.get(i);
+            Fragment fragment = mActive.valueAt(i);
             if (fragment != null && fragment.getAnimatingAway() != null) {
                 // Give up waiting for the animation and just end it.
                 fragment.getAnimatingAway().end();
@@ -2400,7 +2390,7 @@
         if (mHavePendingDeferredStart) {
             boolean loadersRunning = false;
             for (int i=0; i<mActive.size(); i++) {
-                Fragment f = mActive.get(i);
+                Fragment f = mActive.valueAt(i);
                 if (f != null && f.mLoaderManager != null) {
                     loadersRunning |= f.mLoaderManager.hasRunningLoaders();
                 }
@@ -2489,7 +2479,7 @@
         ArrayList<FragmentManagerNonConfig> childFragments = null;
         if (mActive != null) {
             for (int i=0; i<mActive.size(); i++) {
-                Fragment f = mActive.get(i);
+                Fragment f = mActive.valueAt(i);
                 if (f != null) {
                     if (f.mRetainInstance) {
                         if (fragments == null) {
@@ -2594,7 +2584,7 @@
         FragmentState[] active = new FragmentState[N];
         boolean haveFragments = false;
         for (int i=0; i<N; i++) {
-            Fragment f = mActive.get(i);
+            Fragment f = mActive.valueAt(i);
             if (f != null) {
                 if (f.mIndex < 0) {
                     throwException(new IllegalStateException(
@@ -2680,6 +2670,7 @@
         fms.mActive = active;
         fms.mAdded = added;
         fms.mBackStack = backStack;
+        fms.mNextFragmentIndex = mNextFragmentIndex;
         if (mPrimaryNav != null) {
             fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
         }
@@ -2722,10 +2713,7 @@
         
         // Build the full list of active fragments, instantiating them from
         // their saved state.
-        mActive = new ArrayList<>(fms.mActive.length);
-        if (mAvailIndices != null) {
-            mAvailIndices.clear();
-        }
+        mActive = new SparseArray<>(fms.mActive.length);
         for (int i=0; i<fms.mActive.length; i++) {
             FragmentState fs = fms.mActive[i];
             if (fs != null) {
@@ -2735,18 +2723,11 @@
                 }
                 Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);
                 if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
-                mActive.add(f);
+                mActive.put(f.mIndex, f);
                 // Now that the fragment is instantiated (or came from being
                 // retained above), clear mInstance in case we end up re-restoring
                 // from this FragmentState again.
                 fs.mInstance = null;
-            } else {
-                mActive.add(null);
-                if (mAvailIndices == null) {
-                    mAvailIndices = new ArrayList<>();
-                }
-                if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
-                mAvailIndices.add(i);
             }
         }
         
@@ -2757,9 +2738,8 @@
             for (int i = 0; i < count; i++) {
                 Fragment f = nonConfigFragments.get(i);
                 if (f.mTargetIndex >= 0) {
-                    if (f.mTargetIndex < mActive.size()) {
-                        f.mTarget = mActive.get(f.mTargetIndex);
-                    } else {
+                    f.mTarget = mActive.get(f.mTargetIndex);
+                    if (f.mTarget == null) {
                         Log.w(TAG, "Re-attaching retained fragment " + f
                                 + " target no longer exists: " + f.mTargetIndex);
                         f.mTarget = null;
@@ -2813,8 +2793,25 @@
         if (fms.mPrimaryNavActiveIndex >= 0) {
             mPrimaryNav = mActive.get(fms.mPrimaryNavActiveIndex);
         }
+
+        mNextFragmentIndex = fms.mNextFragmentIndex;
     }
-    
+
+    /**
+     * To prevent list modification errors, mActive sets values to null instead of
+     * removing them when the Fragment becomes inactive. This cleans up the list at the
+     * end of executing the transactions.
+     */
+    private void burpActive() {
+        if (mActive != null) {
+            for (int i = mActive.size() - 1; i >= 0; i--) {
+                if (mActive.valueAt(i) == null) {
+                    mActive.delete(mActive.keyAt(i));
+                }
+            }
+        }
+    }
+
     public void attachController(FragmentHostCallback<?> host, FragmentContainer container,
             Fragment parent) {
         if (mHost != null) throw new IllegalStateException("Already attached");
@@ -3051,8 +3048,8 @@
     }
 
     public void setPrimaryNavigationFragment(Fragment f) {
-        if (f != null && (f.getFragmentManager() != this || f.mIndex >= mActive.size()
-                || mActive.get(f.mIndex) != f)) {
+        if (f != null && (mActive.get(f.mIndex) != f
+                || (f.mHost != null && f.getFragmentManager() != this))) {
             throw new IllegalArgumentException("Fragment " + f
                     + " is not an active fragment of FragmentManager " + this);
         }
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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 124749a..f719749 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -93,6 +93,7 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Build;
+import android.os.Debug;
 import android.os.DropBoxManager;
 import android.os.HardwarePropertiesManager;
 import android.os.IBatteryPropertiesRegistrar;
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/VrManager.java b/core/java/android/app/VrManager.java
index a0b0eea..4dd578e 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -6,6 +6,8 @@
 import android.os.RemoteException;
 import android.service.vr.IVrManager;
 
+import java.io.FileDescriptor;
+
 /**
  * Used to control aspects of a devices Virtual Reality (VR) capabilities.
  * <p>
@@ -41,4 +43,32 @@
             e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Initiate connection for system controller data.
+     *
+     * @param fd Controller data file descriptor.
+     *
+     * {@hide}
+     */
+    public void connectController(FileDescriptor fd) {
+        try {
+            mService.connectController(fd);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sever connection for system controller data.
+     *
+     * {@hide}
+     */
+    public void disconnectController() {
+        try {
+            mService.disconnectController();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
 }
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..aa61ce2 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -135,12 +135,13 @@
     /**
      * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation.
      */
-    private final IBluetoothGattCallbackExt mBluetoothGattCallback =
-        new IBluetoothGattCallbackExt.Stub() {
+    private final IBluetoothGattCallback mBluetoothGattCallback =
+        new IBluetoothGattCallback.Stub() {
             /**
              * 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..b35a593 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -59,12 +59,13 @@
     /**
      * Bluetooth GATT interface callbacks
      */
-    private final IBluetoothGattServerCallbackExt mBluetoothGattServerCallback =
-        new IBluetoothGattServerCallbackExt.Stub() {
+    private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
+        new IBluetoothGattServerCallback.Stub() {
             /**
              * 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/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 652a1c6..0825ee8 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -29,8 +29,8 @@
 import android.os.ParcelUuid;
 import android.os.WorkSource;
 
-import android.bluetooth.IBluetoothGattCallbackExt;
-import android.bluetooth.IBluetoothGattServerCallbackExt;
+import android.bluetooth.IBluetoothGattCallback;
+import android.bluetooth.IBluetoothGattServerCallback;
 import android.bluetooth.le.IAdvertiserCallback;
 import android.bluetooth.le.IAdvertisingSetCallback;
 import android.bluetooth.le.IPeriodicAdvertisingCallback;
@@ -66,7 +66,7 @@
     void registerSync(in ScanResult scanResult, in int skip, in int timeout, in IPeriodicAdvertisingCallback callback);
     void unregisterSync(in IPeriodicAdvertisingCallback callback);
 
-    void registerClient(in ParcelUuid appId, in IBluetoothGattCallbackExt callback);
+    void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
 
     void unregisterClient(in int clientIf);
     void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport, in int phy);
@@ -88,7 +88,7 @@
     void configureMTU(in int clientIf, in String address, in int mtu);
     void connectionParameterUpdate(in int clientIf, in String address, in int connectionPriority);
 
-    void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallbackExt callback);
+    void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallback callback);
     void unregisterServer(in int serverIf);
     void serverConnect(in int serverIf, in String address, in boolean isDirect, in int transport);
     void serverDisconnect(in int serverIf, in String address);
diff --git a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
similarity index 90%
rename from core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
rename to core/java/android/bluetooth/IBluetoothGattCallback.aidl
index 736f4b2..4f85cdd 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -22,7 +22,7 @@
  * Callback definitions for interacting with BLE / GATT
  * @hide
  */
-oneway interface IBluetoothGattCallbackExt {
+oneway interface IBluetoothGattCallback {
     void onClientRegistered(in int status, in int clientIf);
     void onClientConnectionState(in int status, in int clientIf,
                                  in boolean connected, in String address);
@@ -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/IBluetoothGattServerCallback.aidl
similarity index 91%
rename from core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
rename to core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
index 091ffb3..74ee11f 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
@@ -21,7 +21,7 @@
  * Callback definitions for interacting with BLE / GATT
  * @hide
  */
-oneway interface IBluetoothGattServerCallbackExt {
+oneway interface IBluetoothGattServerCallback {
     void onServerRegistered(in int status, in int serverIf);
     void onServerConnectionState(in int status, in int serverIf,
                                  in boolean connected, in String address);
@@ -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/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index bb844a3..919f4ba 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -27,6 +27,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A request for the user to select a companion device to associate with.
@@ -69,6 +70,20 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AssociationRequest that = (AssociationRequest) o;
+        return mSingleDevice == that.mSingleDevice &&
+                Objects.equals(mDeviceFilters, that.mDeviceFilters);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSingleDevice, mDeviceFilters);
+    }
+
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeByte((byte) (mSingleDevice ? 1 : 0));
         dest.writeParcelableList(mDeviceFilters, flags);
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 1d8df7f..84e1536 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -35,6 +35,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -123,6 +124,22 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BluetoothDeviceFilter that = (BluetoothDeviceFilter) o;
+        return Objects.equals(mNamePattern, that.mNamePattern) &&
+                Objects.equals(mAddress, that.mAddress) &&
+                Objects.equals(mServiceUuids, that.mServiceUuids) &&
+                Objects.equals(mServiceUuidMasks, that.mServiceUuidMasks);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNamePattern, mAddress, mServiceUuids, mServiceUuidMasks);
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.java b/core/java/android/companion/BluetoothLEDeviceFilter.java
index e057fbc..0444775 100644
--- a/core/java/android/companion/BluetoothLEDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLEDeviceFilter.java
@@ -36,6 +36,8 @@
 import com.android.internal.util.ObjectUtils;
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -160,9 +162,39 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BluetoothLEDeviceFilter that = (BluetoothLEDeviceFilter) o;
+        return mRenameBytesFrom == that.mRenameBytesFrom &&
+                mRenameBytesTo == that.mRenameBytesTo &&
+                mRenameBytesReverseOrder == that.mRenameBytesReverseOrder &&
+                Objects.equals(mNamePattern, that.mNamePattern) &&
+                Objects.equals(mScanFilter, that.mScanFilter) &&
+                Arrays.equals(mRawDataFilter, that.mRawDataFilter) &&
+                Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) &&
+                Objects.equals(mRenamePrefix, that.mRenamePrefix) &&
+                Objects.equals(mRenameSuffix, that.mRenameSuffix);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask,
+                mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesTo,
+                mRenameBytesReverseOrder);
+    }
+
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(patternToString(getNamePattern()));
         dest.writeParcelable(mScanFilter, flags);
+        dest.writeByteArray(mRawDataFilter);
+        dest.writeByteArray(mRawDataFilterMask);
+        dest.writeString(mRenamePrefix);
+        dest.writeString(mRenameSuffix);
+        dest.writeInt(mRenameBytesFrom);
+        dest.writeInt(mRenameBytesTo);
+        dest.writeBoolean(mRenameBytesReverseOrder);
     }
 
     @Override
@@ -174,13 +206,23 @@
             = new Creator<BluetoothLEDeviceFilter>() {
         @Override
         public BluetoothLEDeviceFilter createFromParcel(Parcel in) {
-            return new BluetoothLEDeviceFilter.Builder()
+            Builder builder = new Builder()
                     .setNamePattern(patternFromString(in.readString()))
-                    .setScanFilter(in.readParcelable(null))
-                    .setRawDataFilter(in.readBlob(), in.readBlob())
-                    .setRename(in.readString(), in.readString(),
-                            in.readInt(), in.readInt(), in.readBoolean())
-                    .build();
+                    .setScanFilter(in.readParcelable(null));
+            byte[] rawDataFilter = in.createByteArray();
+            byte[] rawDataFilterMask = in.createByteArray();
+            if (rawDataFilter != null) {
+                builder.setRawDataFilter(rawDataFilter, rawDataFilterMask);
+            }
+            String renamePrefix = in.readString();
+            String suffix = in.readString();
+            int bytesFrom = in.readInt();
+            int bytesTo = in.readInt();
+            boolean bytesReverseOrder = in.readBoolean();
+            if (renamePrefix != null) {
+                builder.setRename(renamePrefix, suffix, bytesFrom, bytesTo, bytesReverseOrder);
+            }
+            return builder.build();
         }
 
         @Override
@@ -240,12 +282,14 @@
          */
         @NonNull
         public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
-                @NonNull byte[] rawDataFilterMask) {
+                @Nullable byte[] rawDataFilterMask) {
             checkNotUsed();
-            checkArgument(rawDataFilter.length == rawDataFilterMask.length,
+            Preconditions.checkNotNull(rawDataFilter);
+            checkArgument(rawDataFilterMask == null ||
+                    rawDataFilter.length == rawDataFilterMask.length,
                     "Mask and filter should be the same length");
-            mRawDataFilter = Preconditions.checkNotNull(rawDataFilter);
-            mRawDataFilterMask = Preconditions.checkNotNull(rawDataFilterMask);
+            mRawDataFilter = rawDataFilter;
+            mRawDataFilterMask = rawDataFilterMask;
             return this;
         }
 
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index ecdc0ce..7b38863 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -17,6 +17,8 @@
 package android.companion;
 
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -24,7 +26,6 @@
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -43,7 +44,7 @@
  */
 public final class CompanionDeviceManager {
 
-    private static final boolean DEBUG = false; //TODO
+    private static final boolean DEBUG = false;
     private static final String LOG_TAG = "CompanionDeviceManager";
 
     /**
@@ -129,10 +130,9 @@
         if (!checkFeaturePresent()) {
             return;
         }
-
-        final Handler finalHandler = handler != null
-                ? handler
-                : new Handler(Looper.getMainLooper());
+        checkNotNull(request, "Request cannot be null");
+        checkNotNull(callback, "Callback cannot be null");
+        final Handler finalHandler = Handler.mainIfNull(handler);
         try {
             mService.associate(
                     request,
@@ -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/companion/WifiDeviceFilter.java b/core/java/android/companion/WifiDeviceFilter.java
index 1ab9ce1..b6e704c 100644
--- a/core/java/android/companion/WifiDeviceFilter.java
+++ b/core/java/android/companion/WifiDeviceFilter.java
@@ -29,6 +29,7 @@
 import android.os.Parcel;
 import android.provider.OneTimeUseBuilder;
 
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -75,6 +76,19 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        WifiDeviceFilter that = (WifiDeviceFilter) o;
+        return Objects.equals(mNamePattern, that.mNamePattern);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNamePattern);
+    }
+
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(patternToString(getNamePattern()));
     }
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..d264e09 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,31 @@
         }
     }
 
+    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 = sa.getFloat(
+                R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio);
+        if (aInfo.maxAspectRatio < 1.0f && aInfo.maxAspectRatio != 0) {
+            // Ignore any value lesser than 1.0.
+            aInfo.maxAspectRatio = 0;
+        }
+    }
+
     /**
      * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
      * @param restartOnConfigChanges The bit mask restartOnConfigChanges fetched from
@@ -4512,6 +4544,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/Camera.java b/core/java/android/hardware/Camera.java
index acf0677..061346c 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -178,24 +178,39 @@
     private static final int NO_ERROR = 0;
 
     /**
-     * @deprecated This broadcast is no longer delivered by the system; use
-     * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
-     * instead.
      * Broadcast Action:  A new picture is taken by the camera, and the entry of
      * the picture has been added to the media store.
      * {@link android.content.Intent#getData} is URI of the picture.
+     *
+     * <p>In {@link android.os.Build.VERSION_CODES#N Android N} this broadcast was removed, and
+     * applications are recommended to use
+     * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
+     * instead.</p>
+     *
+     * <p>In {@link android.os.Build.VERSION_CODES#O Android O} this broadcast has been brought
+     * back, but only for <em>registered</em> receivers.  Apps that are actively running can
+     * against listen to the broadcast if they want an immediate clear signal about a picture
+     * being taken, however anything doing heavy work (or needing to be launched) as a result of
+     * this should still use JobScheduler.</p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    @Deprecated
     public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
 
     /**
-     * @deprecated This broadcast is no longer delivered by the system; use
-     * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
-     * instead.
      * Broadcast Action:  A new video is recorded by the camera, and the entry
      * of the video has been added to the media store.
      * {@link android.content.Intent#getData} is URI of the video.
+     *
+     * <p>In {@link android.os.Build.VERSION_CODES#N Android N} this broadcast was removed, and
+     * applications are recommended to use
+     * {@link android.app.job.JobInfo.Builder JobInfo.Builder}.{@link android.app.job.JobInfo.Builder#addTriggerContentUri}
+     * instead.</p>
+     *
+     * <p>In {@link android.os.Build.VERSION_CODES#O Android O} this broadcast has been brought
+     * back, but only for <em>registered</em> receivers.  Apps that are actively running can
+     * against listen to the broadcast if they want an immediate clear signal about a video
+     * being taken, however anything doing heavy work (or needing to be launched) as a result of
+     * this should still use JobScheduler.</p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @Deprecated
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/Handler.java b/core/java/android/os/Handler.java
index 3c7c962..8678d95 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.Log;
 import android.util.Printer;
 
@@ -69,6 +71,7 @@
      */
     private static final boolean FIND_POTENTIAL_LEAKS = false;
     private static final String TAG = "Handler";
+    private static Handler MAIN_THREAD_HANDLER = null;
 
     /**
      * Callback interface you can use when instantiating a Handler to avoid
@@ -231,6 +234,21 @@
         mAsynchronous = async;
     }
 
+    /** @hide */
+    @NonNull
+    public static Handler getMain() {
+        if (MAIN_THREAD_HANDLER == null) {
+            MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
+        }
+        return MAIN_THREAD_HANDLER;
+    }
+
+    /** @hide */
+    @NonNull
+    public static Handler mainIfNull(@Nullable Handler handler) {
+        return handler == null ? getMain() : handler;
+    }
+
     /** {@hide} */
     public String getTraceName(Message message) {
         final StringBuilder sb = new StringBuilder();
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/PowerSaveState.java b/core/java/android/os/PowerSaveState.java
index 9269e76..7058a1d 100644
--- a/core/java/android/os/PowerSaveState.java
+++ b/core/java/android/os/PowerSaveState.java
@@ -24,7 +24,17 @@
  * @hide
  */
 public class PowerSaveState implements Parcelable {
+    /**
+     * Whether we should enable battery saver for this service.
+     *
+     * @see com.android.server.power.BatterySaverPolicy.ServiceType
+     */
     public final boolean batterySaverEnabled;
+    /**
+     * Whether the battery saver is enabled globally, which means the data we get from
+     * {@link PowerManager#isPowerSaveMode()}
+     */
+    public final boolean globalBatterySaverEnabled;
     public final int gpsMode;
     public final float brightnessFactor;
 
@@ -32,10 +42,12 @@
         batterySaverEnabled = builder.mBatterySaverEnabled;
         gpsMode = builder.mGpsMode;
         brightnessFactor = builder.mBrightnessFactor;
+        globalBatterySaverEnabled = builder.mGlobalBatterySaverEnabled;
     }
 
     public PowerSaveState(Parcel in) {
         batterySaverEnabled = in.readByte() != 0;
+        globalBatterySaverEnabled = in.readByte() != 0;
         gpsMode = in.readInt();
         brightnessFactor = in.readFloat();
     }
@@ -48,12 +60,14 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeByte((byte) (batterySaverEnabled ? 1 : 0));
+        dest.writeByte((byte) (globalBatterySaverEnabled ? 1 : 0));
         dest.writeInt(gpsMode);
         dest.writeFloat(brightnessFactor);
     }
 
     public static final class Builder {
         private boolean mBatterySaverEnabled = false;
+        private boolean mGlobalBatterySaverEnabled = false;
         private int mGpsMode = 0;
         private float mBrightnessFactor = 0.5f;
 
@@ -64,6 +78,11 @@
             return this;
         }
 
+        public Builder setGlobalBatterySaverEnabled(boolean enabled) {
+            mGlobalBatterySaverEnabled = enabled;
+            return this;
+        }
+
         public Builder setGpsMode(int mode) {
             mGpsMode = mode;
             return this;
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/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 9e56e01..fa90384 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -594,6 +594,15 @@
         public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
 
         /**
+         * Flag indicating that this root can be ejected.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#ejectRoot(ContentResolver, Uri)
+         * @see DocumentsProvider#ejectRoot(String)
+         */
+        public static final int FLAG_SUPPORTS_EJECT = 1 << 5;
+
+        /**
          * Flag indicating that this root is currently empty. This may be used
          * to hide the root when opening documents, but the root will still be
          * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
@@ -641,9 +650,6 @@
          * @hide
          */
         public static final int FLAG_REMOVABLE_USB = 1 << 20;
-
-        /** {@hide} */
-        public static final int FLAG_SUPPORTS_EJECT = 1 << 21;
     }
 
     /**
@@ -1345,35 +1351,30 @@
         client.call(METHOD_REMOVE_DOCUMENT, null, in);
     }
 
-    /** {@hide} */
-    public static boolean ejectRoot(ContentResolver resolver, Uri rootUri) {
+    /**
+     * Ejects the given root. It throws {@link IllegalStateException} when ejection failed.
+     *
+     * @param rootUri root with {@link Root#FLAG_SUPPORTS_EJECT} to be ejected
+     */
+    public static void ejectRoot(ContentResolver resolver, Uri rootUri) {
         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
                 rootUri.getAuthority());
         try {
-            return ejectRoot(client, rootUri);
-        } catch (Exception e) {
-            Log.w(TAG, "Failed to eject root", e);
-            return false;
+            ejectRoot(client, rootUri);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
         } finally {
             ContentProviderClient.releaseQuietly(client);
         }
     }
 
     /** {@hide} */
-    public static boolean ejectRoot(ContentProviderClient client, Uri rootUri)
+    public static void ejectRoot(ContentProviderClient client, Uri rootUri)
             throws RemoteException {
         final Bundle in = new Bundle();
         in.putParcelable(DocumentsContract.EXTRA_URI, rootUri);
 
-        final Bundle out = client.call(METHOD_EJECT_ROOT, null, in);
-
-        if (out == null) {
-            throw new RemoteException("Failed to get a reponse from ejectRoot.");
-        }
-        if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
-            throw new RemoteException("Response did not include result field..");
-        }
-        return out.getBoolean(DocumentsContract.EXTRA_RESULT);
+        client.call(METHOD_EJECT_ROOT, null, in);
     }
 
     /**
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 9e68afb..2a83c4b 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -616,9 +616,14 @@
         throw new UnsupportedOperationException("Search not supported");
     }
 
-    /** {@hide} */
+    /**
+     * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
+     *
+     * @param rootId the root to be ejected.
+     * @see Root#FLAG_SUPPORTS_EJECT
+     */
     @SuppressWarnings("unused")
-    public boolean ejectRoot(String rootId) {
+    public void ejectRoot(String rootId) {
         throw new UnsupportedOperationException("Eject not supported");
     }
 
@@ -947,14 +952,12 @@
         if (METHOD_EJECT_ROOT.equals(method)) {
             // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
             // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
-            // MANAGE_DOCUMENTS here instead
-            getContext().enforceCallingPermission(
-                    android.Manifest.permission.MANAGE_DOCUMENTS, null);
+            // MANAGE_DOCUMENTS or associated URI permission here instead
             final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
-            final String rootId = DocumentsContract.getRootId(rootUri);
-            final boolean ejected = ejectRoot(rootId);
+            enforceWritePermissionInner(rootUri, getCallingPackage(), null);
 
-            out.putBoolean(DocumentsContract.EXTRA_RESULT, ejected);
+            final String rootId = DocumentsContract.getRootId(rootUri);
+            ejectRoot(rootId);
 
             return out;
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 146d2d3..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.
          *
@@ -9741,6 +9777,16 @@
         public static final String DATABASE_DOWNGRADE_REASON = "database_downgrade_reason";
 
         /**
+         * The build id of when the settings database was first created (or re-created due it
+         * being missing).
+         *
+         * Type: string
+         *
+         * @hide
+         */
+        public static final String DATABASE_CREATION_BUILDID = "database_creation_buildid";
+
+        /**
          * Flag to toggle journal mode WAL on or off for the contacts database. WAL is enabled by
          * default. Set to 0 to disable.
          *
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index fce06d6..6eea07d 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -73,5 +73,17 @@
      * currently, else return the display id of the virtual display
      */
     int getCompatibilityDisplayId();
+
+    /**
+     * Initiate connection for system controller data.
+     *
+     * @param fd Controller data file descriptor.
+     */
+    void connectController(in FileDescriptor fd);
+
+    /**
+     * Sever connection for system controller data.
+     */
+    void disconnectController();
 }
 
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index 87231e1..44019c32 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -17,6 +17,7 @@
 package android.util;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.ParcelableException;
 
 import com.android.internal.util.Preconditions;
@@ -55,10 +56,26 @@
         return getCompleteMessage(null, t);
     }
 
+    public static <E extends Throwable> void propagateIfInstanceOf(
+            @Nullable Throwable t, Class<E> c) throws E {
+        if (t != null && c.isInstance(t)) {
+            throw c.cast(t);
+        }
+    }
+
+    /**
+     * @param <E> a checked exception that is ok to throw without wrapping
+     */
+    public static <E extends Exception> RuntimeException propagate(@NonNull Throwable t, Class<E> c)
+            throws E {
+        propagateIfInstanceOf(t, c);
+        return propagate(t);
+    }
+
     public static RuntimeException propagate(@NonNull Throwable t) {
         Preconditions.checkNotNull(t);
-        if (t instanceof Error) throw (Error)t;
-        if (t instanceof RuntimeException) throw (RuntimeException)t;
+        propagateIfInstanceOf(t, Error.class);
+        propagateIfInstanceOf(t, RuntimeException.class);
         throw new RuntimeException(t);
     }
 }
diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java
index 58ad2fd..9ba0f5d 100644
--- a/core/java/android/util/LongSparseArray.java
+++ b/core/java/android/util/LongSparseArray.java
@@ -315,6 +315,7 @@
      * and that multiple keys can map to the same value and this will
      * find only one of them.
      * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
+     * @hide
      */
     public int indexOfValueByValue(E value) {
         if (mGarbage) {
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index c766660..b3400ef 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -363,6 +363,7 @@
      * and that multiple keys can map to the same value and this will
      * find only one of them.
      * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
+     * @hide
      */
     public int indexOfValueByValue(E value) {
         if (mGarbage) {
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/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
index 9397a41..c68c449 100644
--- a/core/java/android/view/textclassifier/SmartSelection.java
+++ b/core/java/android/view/textclassifier/SmartSelection.java
@@ -26,6 +26,11 @@
         System.loadLibrary("textclassifier");
     }
 
+    /** Hints the classifier that this may be a url. */
+    static final int HINT_FLAG_URL = 0x01;
+    /** Hints the classifier that this may be an email. */
+    static final int HINT_FLAG_EMAIL = 0x02;
+
     private final long mCtx;
 
     /**
@@ -59,7 +64,7 @@
      * scores for different collections.
      */
     public ClassificationResult[] classifyText(
-            String context, int selectionBegin, int selectionEnd) {
+            String context, int selectionBegin, int selectionEnd, int hintFlags) {
         return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd);
     }
 
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..66a62c3 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -35,6 +35,7 @@
 import android.text.style.ClickableSpan;
 import android.text.util.Linkify;
 import android.util.Log;
+import android.util.Patterns;
 import android.view.View;
 
 import com.android.internal.util.Preconditions;
@@ -88,7 +89,9 @@
                 if (start >= 0 && end <= string.length() && start <= end) {
                     final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end);
                     final SmartSelection.ClassificationResult[] results =
-                            getSmartSelection().classifyText(string, start, end);
+                            getSmartSelection().classifyText(
+                                    string, start, end,
+                                    getHintFlags(string, start, end));
                     final int size = results.length;
                     for (int i = 0; i < size; i++) {
                         tsBuilder.setEntityType(results[i].mCollection, results[i].mScore);
@@ -116,14 +119,18 @@
         validateInput(text, startIndex, endIndex);
         try {
             if (text.length() > 0) {
-                final CharSequence classified = text.subSequence(startIndex, endIndex);
+                final String string = text.toString();
                 SmartSelection.ClassificationResult[] results = getSmartSelection()
-                        .classifyText(text.toString(), startIndex, endIndex);
+                        .classifyText(string, startIndex, endIndex,
+                                getHintFlags(string, startIndex, endIndex));
                 if (results.length > 0) {
+                    final TextClassificationResult classificationResult =
+                            createClassificationResult(
+                                    results, string.subSequence(startIndex, endIndex));
                     // 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 +156,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) {
@@ -226,6 +213,24 @@
         return builder.build();
     }
 
+    private static int getHintFlags(CharSequence text, int start, int end) {
+        int flag = 0;
+        final CharSequence subText = text.subSequence(start, end);
+        if (Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(subText).matches()) {
+            flag |= SmartSelection.HINT_FLAG_EMAIL;
+        }
+        if (Patterns.AUTOLINK_WEB_URL.matcher(subText).matches()
+                && Linkify.sUrlMatchFilter.acceptMatch(text, start, end)) {
+            flag |= SmartSelection.HINT_FLAG_URL;
+        }
+        // TODO: Added this log for debug only. Remove before release.
+        Log.d(LOG_TAG, String.format("Email hint: %b",
+                (flag & SmartSelection.HINT_FLAG_EMAIL) != 0));
+        Log.d(LOG_TAG, String.format("Url hint: %b",
+                (flag & SmartSelection.HINT_FLAG_URL) != 0));
+        return flag;
+    }
+
     private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) {
         if (types.length < 1) {
             return "";
@@ -280,7 +285,9 @@
                 if (selectionStart >= 0 && selectionEnd <= text.length()
                         && selectionStart <= selectionEnd) {
                     final SmartSelection.ClassificationResult[] results =
-                            smartSelection.classifyText(text, selectionStart, selectionEnd);
+                            smartSelection.classifyText(
+                                    text, selectionStart, selectionEnd,
+                                    getHintFlags(text, selectionStart, selectionEnd));
                     if (results.length > 0) {
                         final String type = getHighestScoringType(results);
                         if (matches(type, linkMask)) {
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index f9d7332..f78d622 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -22,6 +22,7 @@
 import android.os.SystemService;
 import android.os.ZygoteProcess;
 import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -53,6 +54,13 @@
     private static ZygoteProcess sZygote;
 
     /**
+     * Variable that allows us to determine whether the WebView zygote Service has already been
+     * started.
+     */
+    @GuardedBy("sLock")
+    private static boolean sStartedService = false;
+
+    /**
      * Information about the selected WebView package. This is set from #onWebViewProviderChanged().
      */
     @GuardedBy("sLock")
@@ -67,7 +75,9 @@
 
     public static ZygoteProcess getProcess() {
         synchronized (sLock) {
-            connectToZygoteIfNeededLocked();
+            if (sZygote != null) return sZygote;
+
+            waitForServiceStartAndConnect();
             return sZygote;
         }
     }
@@ -95,17 +105,20 @@
             final String serviceName = getServiceNameLocked();
             if (serviceName == null) return;
 
-            if (enabled && sZygote == null) {
-                SystemService.start(serviceName);
+            if (enabled) {
+                if (!sStartedService) {
+                    SystemService.start(serviceName);
+                    sStartedService = true;
+                }
             } else {
                 SystemService.stop(serviceName);
+                sStartedService = false;
                 sZygote = null;
             }
         }
     }
 
     public static void onWebViewProviderChanged(PackageInfo packageInfo) {
-        String serviceName;
         synchronized (sLock) {
             sPackage = packageInfo;
 
@@ -114,7 +127,7 @@
                 return;
             }
 
-            serviceName = getServiceNameLocked();
+            final String serviceName = getServiceNameLocked();
             sZygote = null;
 
             // The service may enter the RUNNING state before it opens the socket,
@@ -124,14 +137,28 @@
             } else {
                 SystemService.restart(serviceName);
             }
+            sStartedService = true;
+        }
+    }
 
-            try {
-                SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000);
-            } catch (TimeoutException e) {
-                Log.e(LOGTAG, "Timed out waiting for " + serviceName);
-                return;
-            }
+    private static void waitForServiceStartAndConnect() {
+        if (!sStartedService) {
+            throw new AndroidRuntimeException("Tried waiting for the WebView Zygote Service to " +
+                    "start running without first starting the service.");
+        }
 
+        String serviceName;
+        synchronized (sLock) {
+            serviceName = getServiceNameLocked();
+        }
+        try {
+            SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000);
+        } catch (TimeoutException e) {
+            Log.e(LOGTAG, "Timed out waiting for " + serviceName);
+            return;
+        }
+
+        synchronized (sLock) {
             connectToZygoteIfNeededLocked();
         }
     }
@@ -151,8 +178,9 @@
 
     @GuardedBy("sLock")
     private static void connectToZygoteIfNeededLocked() {
-        if (sZygote != null)
+        if (sZygote != null) {
             return;
+        }
 
         if (sPackage == null) {
             Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
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..970581e 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));
@@ -2307,7 +2371,8 @@
     }
 
     private class PopupDecorView extends FrameLayout {
-        private TransitionListenerAdapter mPendingExitListener;
+        /** Runnable used to clean up listeners after exit transition. */
+        private Runnable mCleanupAfterExit;
 
         public PopupDecorView(Context context) {
             super(context);
@@ -2418,7 +2483,7 @@
          * <p>
          * <strong>Note:</strong> The transition listener is guaranteed to have
          * its {@code onTransitionEnd} method called even if the transition
-         * never starts; however, it may be called with a {@code null} argument.
+         * never starts.
          */
         public void startExitTransition(@NonNull Transition transition,
                 @Nullable final View anchorRoot, @Nullable final Rect epicenter,
@@ -2434,25 +2499,32 @@
                 anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
             }
 
-            // The exit listener MUST be called for cleanup, even if the
-            // transition never starts or ends. Stash it for later.
-            mPendingExitListener = new TransitionListenerAdapter() {
-                @Override
-                public void onTransitionEnd(Transition t) {
-                    if (anchorRoot != null) {
-                        anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
-                    }
+            // The cleanup runnable MUST be called even if the transition is
+            // canceled before it starts (and thus can't call onTransitionEnd).
+            mCleanupAfterExit = () -> {
+                listener.onTransitionEnd(transition);
 
-                    listener.onTransitionEnd(t);
-
-                    // The listener was called. Our job here is done.
-                    mPendingExitListener = null;
-                    t.removeListener(this);
+                if (anchorRoot != null) {
+                    anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
                 }
+
+                // The listener was called. Our job here is done.
+                mCleanupAfterExit = null;
             };
 
             final Transition exitTransition = transition.clone();
-            exitTransition.addListener(mPendingExitListener);
+            exitTransition.addListener(new TransitionListenerAdapter() {
+                @Override
+                public void onTransitionEnd(Transition t) {
+                    t.removeListener(this);
+
+                    // This null check shouldn't be necessary, but it's easier
+                    // to check here than it is to test every possible case.
+                    if (mCleanupAfterExit != null) {
+                        mCleanupAfterExit.run();
+                    }
+                }
+            });
             exitTransition.setEpicenterCallback(new EpicenterCallback() {
                 @Override
                 public Rect onGetEpicenter(Transition transition) {
@@ -2480,8 +2552,10 @@
         public void cancelTransitions() {
             TransitionManager.endTransitions(this);
 
-            if (mPendingExitListener != null) {
-                mPendingExitListener.onTransitionEnd(null);
+            // If the cleanup runnable is still around, that means the
+            // transition never started. We should run it now to clean up.
+            if (mCleanupAfterExit != null) {
+                mCleanupAfterExit.run();
             }
         }
 
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index a032383..003db06 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -289,7 +289,7 @@
      */
     private static final class TextClassificationHelper {
 
-        private static final int TRIM_DELTA = 50;  // characters
+        private static final int TRIM_DELTA = 120;  // characters
 
         private TextClassifier mTextClassifier;
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 8d0ea08..bc7c79d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2031,6 +2031,8 @@
         Typeface tf = fontTypeface;
         if (tf == null && familyName != null) {
             tf = Typeface.create(familyName, styleIndex);
+        } else if (tf != null && tf.getStyle() != styleIndex) {
+            tf = Typeface.create(tf, styleIndex);
         }
         if (tf != null) {
             setTypeface(tf);
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..3d355ce 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1346,7 +1346,7 @@
     <permission android:name="android.permission.CONNECTIVITY_INTERNAL"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Allows an internal user to use restricted Networks.
+    <!-- @SystemApi Allows an internal user to use restricted Networks.
          @hide -->
     <permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"
         android:protectionLevel="signature|privileged" />
@@ -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/config.xml b/core/res/res/values/config.xml
index 68e766e..7704519 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2580,12 +2580,14 @@
     <integer name="config_defaultPictureInPictureGravity">0x55</integer>
 
     <!-- The minimum aspect ratio (width/height) that is supported for picture-in-picture.  Any
-         ratio smaller than this is considered too tall and thin to be usable. -->
-    <item name="config_pictureInPictureMinAspectRatio" format="float" type="dimen">0.5</item>
+         ratio smaller than this is considered too tall and thin to be usable. Currently, this
+         is the inverse of the max landscape aspect ratio (1:2.39), but this is an extremely
+         skinny aspect ratio that is not expected to be widely used. -->
+    <item name="config_pictureInPictureMinAspectRatio" format="float" type="dimen">0.41841004184</item>
 
     <!-- The minimum aspect ratio (width/height) that is supported for picture-in-picture. Any
-         ratio larger than this is considered to wide and short to be usable. -->
-    <item name="config_pictureInPictureMaxAspectRatio" format="float" type="dimen">2.35</item>
+         ratio larger than this is considered to wide and short to be usable. Currently 2.39:1. -->
+    <item name="config_pictureInPictureMaxAspectRatio" format="float" type="dimen">2.39</item>
 
     <!-- The snap mode to use for picture-in-picture. These values correspond to constants defined
          in PipSnapAlgorithm and should not be changed independently.
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 9205839..1129647 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -530,9 +530,4 @@
     <dimen name="item_touch_helper_max_drag_scroll_per_frame">20dp</dimen>
     <dimen name="item_touch_helper_swipe_escape_velocity">120dp</dimen>
     <dimen name="item_touch_helper_swipe_escape_max_velocity">800dp</dimen>
-
-    <!-- The elevation of AutoFill fill window-->
-    <dimen name="autofill_fill_elevation">4dp</dimen>
-    <dimen name="autofill_fill_item_height">64dp</dimen>
-    <dimen name="autofill_fill_min_margin">16dp</dimen>
 </resources>
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 d1c5900..868e256 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -370,7 +370,7 @@
     <string name="ssl_ca_cert_noti_by_unknown">By an unknown third party</string>
     <!-- Content text for a notification. The Title of the notification is "ssl_ca_cert_warning".
          This indicates that an unspecified administrator is doing the monitoring. [CHAR LIMIT=100]-->
-    <string name="ssl_ca_cert_noti_by_administrator">By your work profile administrator</string>
+    <string name="ssl_ca_cert_noti_by_administrator">By your work profile admin</string>
     <!-- Content text for a notification. The Title of the notification is "ssl_ca_cert_warning".
          This indicates who is doing the monitoring. [CHAR LIMIT=100]-->
     <string name="ssl_ca_cert_noti_managed">By <xliff:g id="managing_domain">%s</xliff:g></string>
@@ -381,15 +381,15 @@
     <!-- Content text for a notification. The Title of the notification is "work_profile_deleted",
          i.e. "Work profile deleted". This says that the profile is deleted by the system as a result of
          the current profile owner gone missing. [CHAR LIMIT=100]-->
-    <string name="work_profile_deleted_description">Work profile deleted due to missing admin app.</string>
+    <string name="work_profile_deleted_description">Work profile deleted due to missing admin app</string>
     <!-- Content text for an expanded notification. The Title of the notification is "work_profile_deleted",
          i.e. "Work profile deleted". This further explains that the profile is deleted by the system
          as a result of the current profile admin gone missing. [CHAR LIMIT=NONE]-->
     <string name="work_profile_deleted_details">The work profile admin app is either missing or corrupted.
-         As a result, your work profile and related data have been deleted. Contact your administrator for assistance.</string>
+         As a result, your work profile and related data have been deleted. Contact your admin for assistance.</string>
     <!-- Content text for a notification. The Title of the notification is "work_profile_deleted",
         This indicates that a work profile has been deleted. [CHAR LIMIT=NONE]-->
-    <string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device.</string>
+    <string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device</string>
 
     <!-- Content title for a notification. This notification indicates that the device is managed
          and network logging was activated by a device owner. [CHAR LIMIT=NONE]-->
@@ -403,8 +403,8 @@
     <string name="factory_reset_warning">Your device will be erased</string>
     <!-- Text message in the factory reset warning dialog. This says that the the device admin app
          is missing or corrupted. As a result the device will be erased. [CHAR LIMIT=NONE]-->
-    <string name="factory_reset_message">The admin app is missing components or corrupted, and can\'t be used.
-         Your device will now be erased. Contact your administrator for assistance.</string>
+    <string name="factory_reset_message">The admin app can\'t be used. Your device will now be
+        erased.\n\nIf you have questions, contact your organization's admin.</string>
 
     <!-- Display name for any time a piece of data refers to the owner of the phone. For example, this could be used in place of the phone's phone number. -->
     <string name="me">Me</string>
@@ -1460,7 +1460,7 @@
     <!-- Description of policy access to limiting the user's password choices -->
     <string name="policydesc_limitPassword">Control the length and the characters allowed in screen lock passwords and PINs.</string>
     <!-- Title of policy access to watch user login attempts -->
-    <string name="policylab_watchLogin">Monitor screen-unlock attempts</string>
+    <string name="policylab_watchLogin">Monitor screen unlock attempts</string>
     <!-- Description of policy access to watch user login attempts -->
     <string name="policydesc_watchLogin" product="tablet">Monitor the number of incorrect passwords
     typed when unlocking the screen, and lock the tablet or erase all the tablet\'s
@@ -2385,7 +2385,7 @@
          It is also used by the home screen's search "widget". It should be short -->
     <string name="search_go">Search</string>
     <!-- Default hint text for the system-wide search UI's text field. [CHAR LIMIT=30] -->
-    <string name="search_hint">Search…</string>
+    <string name="search_hint">Search\u2026</string>
     <!-- SearchView accessibility description for search button [CHAR LIMIT=NONE] -->
     <string name="searchview_description_search">Search</string>
     <!-- SearchView accessibility description for search text field [CHAR LIMIT=NONE] -->
@@ -3135,7 +3135,8 @@
     <!-- Title of notification shown to indicate that bug report is still being collected after sharing was accepted. -->
     <string name="sharing_remote_bugreport_notification_title">Sharing bug report\u2026</string>
     <!-- Message of a notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
-    <string name="share_remote_bugreport_notification_message_finished">Your IT admin requested a bug report to help troubleshoot this device. Apps and data may be shared.</string>
+    <string name="share_remote_bugreport_notification_message_finished">Your admin requested a bug
+        report to help troubleshoot this device. Apps and data may be shared.</string>
     <!-- Acceptance label of notification shown to ask for user consent for sharing the remote bugreport. -->
     <string name="share_remote_bugreport_action">SHARE</string>
     <!-- Decline label of notification shown to ask for user consent for sharing the remote bugreport. -->
@@ -3928,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.-->
@@ -3955,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] -->
@@ -3966,7 +3978,7 @@
     <!-- Error message title [CHAR LIMIT=35] -->
     <string name="error_message_title">Error</string>
     <!-- Message informing user that the change was disallowed by an administrator. [CHAR LIMIT=none] -->
-    <string name="error_message_change_not_allowed">This change isn\'t allowed by your administrator</string>
+    <string name="error_message_change_not_allowed">This change isn\'t allowed by your admin</string>
     <!-- Message informing user that the requested activity could not be found [CHAR LIMIT=none] -->
     <string name="app_not_found">No application found to handle this action</string>
     <string name="revoke">Revoke</string>
@@ -4169,7 +4181,7 @@
     <string name="print_service_installed_message">Tap to enable</string>
 
     <!-- PIN entry dialog title for entering the administrator PIN [CHAR LIMIT=none] -->
-    <string name="restr_pin_enter_admin_pin">Enter administrator PIN</string>
+    <string name="restr_pin_enter_admin_pin">Enter admin PIN</string>
     <!-- PIN entry dialog label/hint for PIN [CHAR LIMIT=none] -->
     <string name="restr_pin_enter_pin">Enter PIN</string>
     <!-- PIN entry dialog label/hint for incorrect PIN entry [CHAR LIMIT=none] -->
@@ -4255,9 +4267,11 @@
     <string name="date_picker_day_typeface">sans-serif-medium</string>
 
     <!-- Notify use that they are in Lock-to-app -->
-    <string name="lock_to_app_toast">To unpin this screen, touch &amp; hold Back and Overview.</string>
+    <string name="lock_to_app_toast">To unpin this screen, touch &amp; hold Back and Overview
+        buttons</string>
+
     <!-- Notify user that they are locked in lock-to-app mode -->
-    <string name="lock_to_app_toast_locked">App is pinned: Unpinning isn\'t allowed on this device.</string>
+    <string name="lock_to_app_toast_locked">This app can\'t be unpinned</string>
     <!-- Starting lock-to-app indication. -->
     <string name="lock_to_app_start">Screen pinned</string>
     <!-- Exting lock-to-app indication. -->
@@ -4271,11 +4285,11 @@
     <string name="lock_to_app_unlock_password">Ask for password before unpinning</string>
 
     <!-- Notification shown when device owner silently installs a package [CHAR LIMIT=NONE] -->
-    <string name="package_installed_device_owner">Installed by your administrator</string>
+    <string name="package_installed_device_owner">Installed by your admin</string>
     <!-- Notification shown when device owner silently updates a package [CHAR LIMIT=NONE] -->
-    <string name="package_updated_device_owner">Updated by your administrator</string>
+    <string name="package_updated_device_owner">Updated by your admin</string>
     <!-- Notification shown when device owner silently deletes a package [CHAR LIMIT=NONE] -->
-    <string name="package_deleted_device_owner">Deleted by your administrator</string>
+    <string name="package_deleted_device_owner">Deleted by your admin</string>
 
     <!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
     <string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration, location services, and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
@@ -4462,9 +4476,10 @@
     <string name="locale_search_menu">Search</string>
 
     <!-- Title for dialog displayed when work profile is turned off. [CHAR LIMIT=30] -->
-    <string name="work_mode_off_title">Work mode is OFF</string>
+    <string name="work_mode_off_title">Turn on work mode?</string>
     <!-- Message displayed in dialog when work profile is turned off. [CHAR LIMIT=NONE] -->
-    <string name="work_mode_off_message">Allow work profile to function, including apps, background sync, and related features.</string>
+    <string name="work_mode_off_message">This will turn on your work profile, including apps,
+        background sync, and related features</string>
     <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
     <string name="work_mode_turn_on">Turn on</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 11cde7a5..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"/>
@@ -2848,9 +2857,8 @@
   <java-symbol type="dimen" name="item_touch_helper_swipe_escape_max_velocity"/>
 
   <!-- com.android.server.autofill -->
-  <java-symbol type="dimen" name="autofill_fill_elevation" />
-  <java-symbol type="dimen" name="autofill_fill_item_height" />
-  <java-symbol type="dimen" name="autofill_fill_min_margin" />
+  <!-- TODO: floating_window_z temporary exposed until Autofill UI uses a ContextThemeWrapper -->
+  <java-symbol type="dimen" name="floating_window_z" />
   <java-symbol type="layout" name="autofill_save"/>
   <java-symbol type="layout" name="autofill_dataset_picker"/>
   <java-symbol type="id" name="autofill" />
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 0cfdaf5..782a50f 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -141,6 +141,7 @@
                     Settings.Global.CONTACTS_DATABASE_WAL_ENABLED,
                     Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
                     Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+                    Settings.Global.DATABASE_CREATION_BUILDID,
                     Settings.Global.DATABASE_DOWNGRADE_REASON,
                     Settings.Global.DATA_ROAMING,
                     Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
@@ -327,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/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index e8972aa..d154730 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -363,6 +363,7 @@
     state.computedState.transform.copyTo(&info.transform[0]);
 
     mRenderState.invokeFunctor(op.functor, DrawGlInfo::kModeDraw, &info);
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
 }
 
 void BakedOpRenderer::dirtyRenderTarget(const Rect& uiDirty) {
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 7be71ee..8126d57 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -264,10 +264,15 @@
             // the actual time spent blocked.
             nsecs_t forgiveAmount = std::min(expectedDequeueDuration,
                     frame[FrameInfoIndex::DequeueBufferDuration]);
+            LOG_ALWAYS_FATAL_IF(forgiveAmount >= totalDuration,
+                    "Impossible dequeue duration! dequeue duration reported %" PRId64
+                    ", total duration %" PRId64, forgiveAmount, totalDuration);
             totalDuration -= forgiveAmount;
         }
     }
+    LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
     uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
+    LOG_ALWAYS_FATAL_IF(framebucket < 0, "framebucket < 0 (%u)", framebucket);
     // Keep the fast path as fast as possible.
     if (CC_LIKELY(totalDuration < mFrameInterval)) {
         mData->frameCounts[framebucket]++;
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 2e22132..611fdd1 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.media.MediaCasException.*;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -28,6 +29,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 import android.util.Singleton;
 
@@ -84,8 +86,6 @@
  * sessionId of the descrambler can be retrieved by {@link MediaExtractor#getDrmInitData}
  * and used to initialize a MediaDescrambler object for MediaCodec.
  * <p>
- * TODO: determine exception handling schemes.
- * <p>
  * <h3>Listeners</h3>
  * <p>The app may register a listener to receive events from the CA system using
  * method {@link #setEventListener}. The exact format of the event is scheme-specific
@@ -382,28 +382,22 @@
         mEventHandler = new EventHandler(looper);
     }
 
-    /*
-     * TODO: handle ServiceSpecificException from the IMediaCas
-     * All Drm-specific failures will be thrown by mICas as
-     * ServiceSpecificException exception with Drm error code.
-     * These need to be re-thrown as crypto exceptions.
-     */
-
     /**
      * Send the private data for the CA system.
      *
      * @param data byte array of the private data.
      *
      * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    /*
-     * TODO: need to re-throw DRM-specific exceptions
-     */
-    public void setPrivateData(@NonNull byte[] data) {
+    public void setPrivateData(@NonNull byte[] data) throws MediaCasException {
         validateInternalStates();
 
         try {
             mICas.setPrivateData(data);
+        } catch (ServiceSpecificException e) {
+            MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -416,14 +410,17 @@
      *
      * @return session id of the newly opened session.
      *
-     * @throws IllegalStateException if the MediaCas instance is not valid,
-     * or IllegalArgumentException if a session for the program already exists.
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    public byte[] openSession(int programNumber) {
+    public byte[] openSession(int programNumber) throws MediaCasException {
         validateInternalStates();
 
         try {
             return mICas.openSession(programNumber);
+        } catch (ServiceSpecificException e) {
+            MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -438,14 +435,18 @@
      *
      * @return session id of the newly opened session.
      *
-     * @throws IllegalStateException if the MediaCas instance is not valid,
-     * or IllegalArgumentException if a session for the stream already exists.
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    public byte[] openSession(int programNumber, int elementaryPID) {
+    public byte[] openSession(int programNumber, int elementaryPID)
+            throws MediaCasException {
         validateInternalStates();
 
         try {
             return mICas.openSessionForStream(programNumber, elementaryPID);
+        } catch (ServiceSpecificException e) {
+            MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -457,14 +458,16 @@
      *
      * @param sessionId the session to be closed.
      *
-     * @throws IllegalStateException if the MediaCas instance is not valid,
-     * or IllegalArgumentException if the session is not valid.
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
     public void closeSession(@NonNull byte[] sessionId) {
         validateInternalStates();
 
         try {
             mICas.closeSession(sessionId);
+        } catch (ServiceSpecificException e) {
+            MediaCasStateException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -476,17 +479,18 @@
      * @param sessionId the session for which the private data is intended.
      * @param data byte array of the private data.
      *
-     * @throws IllegalStateException if the MediaCas instance is not valid,
-     * or IllegalArgumentException if the session is not valid.
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    /*
-     * TODO: need to re-throw DRM-specific exceptions
-     */
-    public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data) {
+    public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data)
+            throws MediaCasException {
         validateInternalStates();
 
         try {
             mICas.setSessionPrivateData(sessionId, data);
+        } catch (ServiceSpecificException e) {
+            MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -500,19 +504,19 @@
      * @param offset position within data where the ECM data begins.
      * @param length length of the data (starting from offset).
      *
-     * @throws IllegalStateException if the MediaCas instance is not valid,
-     * or IllegalArgumentException if the session is not valid.
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    /*
-     * TODO: need to re-throw DRM-specific exceptions
-     */
-    public void processEcm(
-            @NonNull byte[] sessionId, @NonNull byte[] data, int offset, int length) {
+    public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data,
+            int offset, int length) throws MediaCasException {
         validateInternalStates();
 
         try {
             mCasData.set(data, offset, length);
             mICas.processEcm(sessionId, mCasData);
+        } catch (ServiceSpecificException e) {
+            MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -526,13 +530,12 @@
      * @param sessionId the session for which the ECM is intended.
      * @param data byte array of the ECM data.
      *
-     * @throws IllegalStateException if the MediaCas instance is not valid,
-     * or IllegalArgumentException if the session is not valid.
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    /*
-     * TODO: need to re-throw DRM-specific exceptions
-     */
-    public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data) {
+    public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data)
+            throws MediaCasException {
         processEcm(sessionId, data, 0, data.length);
     }
 
@@ -544,16 +547,18 @@
      * @param length length of the data (starting from offset).
      *
      * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    /*
-     * TODO: need to re-throw DRM-specific exceptions
-     */
-    public void processEmm(@NonNull byte[] data, int offset, int length) {
+    public void processEmm(@NonNull byte[] data, int offset, int length)
+            throws MediaCasException {
         validateInternalStates();
 
         try {
             mCasData.set(data, offset, length);
             mICas.processEmm(mCasData);
+        } catch (ServiceSpecificException e) {
+            MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -567,11 +572,10 @@
      * @param data byte array of the EMM data.
      *
      * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    /*
-     * TODO: need to re-throw DRM-specific exceptions
-     */
-    public void processEmm(@NonNull byte[] data) {
+    public void processEmm(@NonNull byte[] data) throws MediaCasException {
         processEmm(data, 0, data.length);
     }
 
@@ -584,12 +588,17 @@
      * @param data a byte array containing scheme-specific data for the event.
      *
      * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    public void sendEvent(int event, int arg, @Nullable byte[] data) {
+    public void sendEvent(int event, int arg, @Nullable byte[] data)
+            throws MediaCasException {
         validateInternalStates();
 
         try {
             mICas.sendEvent(event, arg, data);
+        } catch (ServiceSpecificException e) {
+            MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -603,12 +612,16 @@
      * specific.
      *
      * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    public void provision(@NonNull String provisionString) {
+    public void provision(@NonNull String provisionString) throws MediaCasException {
         validateInternalStates();
 
         try {
             mICas.provision(provisionString);
+        } catch (ServiceSpecificException e) {
+            MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -621,15 +634,17 @@
      * @param refreshData private data associated with the refreshment.
      *
      * @throws IllegalStateException if the MediaCas instance is not valid.
+     * @throws MediaCasException for CAS-specific errors.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    /*
-     * TODO: define enums for refreshType
-     */
-    public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData) {
+    public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData)
+            throws MediaCasException {
         validateInternalStates();
 
         try {
             mICas.refreshEntitlements(refreshType, refreshData);
+        } catch (ServiceSpecificException e) {
+            MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
diff --git a/media/java/android/media/MediaCasException.java b/media/java/android/media/MediaCasException.java
index 1d5d3cd..485f6ee 100644
--- a/media/java/android/media/MediaCasException.java
+++ b/media/java/android/media/MediaCasException.java
@@ -16,11 +16,104 @@
 
 package android.media;
 
+import android.os.ServiceSpecificException;
+
 /**
  * Base class for MediaCas exceptions
  */
 public class MediaCasException extends Exception {
+
+    /** @hide */
+    public static final int DRM_ERROR_BASE = -2000;
+    /** @hide */
+    public static final int ERROR_DRM_UNKNOWN                        = DRM_ERROR_BASE;
+    /** @hide */
+    public static final int ERROR_DRM_NO_LICENSE                     = DRM_ERROR_BASE - 1;
+    /** @hide */
+    public static final int ERROR_DRM_LICENSE_EXPIRED                = DRM_ERROR_BASE - 2;
+    /** @hide */
+    public static final int ERROR_DRM_SESSION_NOT_OPENED             = DRM_ERROR_BASE - 3;
+    /** @hide */
+    public static final int ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED   = DRM_ERROR_BASE - 4;
+    /** @hide */
+    public static final int ERROR_DRM_DECRYPT                        = DRM_ERROR_BASE - 5;
+    /** @hide */
+    public static final int ERROR_DRM_CANNOT_HANDLE                  = DRM_ERROR_BASE - 6;
+    /** @hide */
+    public static final int ERROR_DRM_TAMPER_DETECTED                = DRM_ERROR_BASE - 7;
+    /** @hide */
+    public static final int ERROR_DRM_NOT_PROVISIONED                = DRM_ERROR_BASE - 8;
+    /** @hide */
+    public static final int ERROR_DRM_DEVICE_REVOKED                 = DRM_ERROR_BASE - 9;
+    /** @hide */
+    public static final int ERROR_DRM_RESOURCE_BUSY                  = DRM_ERROR_BASE - 10;
+    /** @hide */
+    public static final int ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION = DRM_ERROR_BASE - 11;
+    /** @hide */
+    public static final int ERROR_DRM_LAST_USED_ERRORCODE            = DRM_ERROR_BASE - 11;
+    /** @hide */
+    public static final int ERROR_DRM_VENDOR_MAX                     = DRM_ERROR_BASE - 500;
+    /** @hide */
+    public static final int ERROR_DRM_VENDOR_MIN                     = DRM_ERROR_BASE - 999;
+
+    /** @hide */
     public MediaCasException(String detailMessage) {
         super(detailMessage);
     }
+
+    static void throwExceptions(ServiceSpecificException e) throws MediaCasException {
+        if (e.errorCode == ERROR_DRM_NOT_PROVISIONED) {
+            throw new NotProvisionedException(e.getMessage());
+        } else if (e.errorCode == ERROR_DRM_RESOURCE_BUSY) {
+            throw new ResourceBusyException(e.getMessage());
+        } else if (e.errorCode == ERROR_DRM_DEVICE_REVOKED) {
+            throw new DeniedByServerException(e.getMessage());
+        } else {
+            MediaCasStateException.throwExceptions(e);
+        }
+    }
+
+    /**
+     * Exception thrown when an attempt is made to construct a MediaCas object
+     * using a CA_system_id that is not supported by the device
+     */
+    public static final class UnsupportedCasException extends MediaCasException {
+        /** @hide */
+        public UnsupportedCasException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
+
+    /**
+     * Exception thrown when an operation on a MediaCas object is attempted
+     * before it's provisioned successfully.
+     */
+    public static final class NotProvisionedException extends MediaCasException {
+        /** @hide */
+        public NotProvisionedException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
+
+    /**
+     * Exception thrown when the provisioning server or key server denies a
+     * license for a device.
+     */
+    public static final class DeniedByServerException extends MediaCasException {
+        /** @hide */
+        public DeniedByServerException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
+
+    /**
+     * Exception thrown when an operation on a MediaCas object is attempted
+     * and hardware resources are not available, due to being in use.
+     */
+    public static final class ResourceBusyException extends MediaCasException {
+        /** @hide */
+        public ResourceBusyException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
 }
diff --git a/media/java/android/media/MediaCasStateException.java b/media/java/android/media/MediaCasStateException.java
new file mode 100644
index 0000000..cf05c29
--- /dev/null
+++ b/media/java/android/media/MediaCasStateException.java
@@ -0,0 +1,93 @@
+/*
+ * 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ServiceSpecificException;
+
+import static android.media.MediaCasException.*;
+
+/**
+ * Base class for MediaCas runtime exceptions
+ */
+public class MediaCasStateException extends IllegalStateException {
+    private final int mErrorCode;
+    private final String mDiagnosticInfo;
+
+    /** @hide */
+    public MediaCasStateException(int err, @Nullable String msg, @Nullable String diagnosticInfo) {
+        super(msg);
+        mErrorCode = err;
+        mDiagnosticInfo = diagnosticInfo;
+    }
+
+    static void throwExceptions(ServiceSpecificException e) {
+        String diagnosticInfo = "";
+        switch (e.errorCode) {
+        case ERROR_DRM_UNKNOWN:
+            diagnosticInfo = "General CAS error";
+            break;
+        case ERROR_DRM_NO_LICENSE:
+            diagnosticInfo = "No license";
+            break;
+        case ERROR_DRM_LICENSE_EXPIRED:
+            diagnosticInfo = "License expired";
+            break;
+        case ERROR_DRM_SESSION_NOT_OPENED:
+            diagnosticInfo = "Session not opened";
+            break;
+        case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED:
+            diagnosticInfo = "Not initialized";
+            break;
+        case ERROR_DRM_DECRYPT:
+            diagnosticInfo = "Decrypt error";
+            break;
+        case ERROR_DRM_CANNOT_HANDLE:
+            diagnosticInfo = "Unsupported scheme or data format";
+            break;
+        case ERROR_DRM_TAMPER_DETECTED:
+            diagnosticInfo = "Tamper detected";
+            break;
+        default:
+            diagnosticInfo = "Unknown CAS state exception";
+            break;
+        }
+        throw new MediaCasStateException(e.errorCode, e.getMessage(),
+                String.format("%s (err=%d)", diagnosticInfo, e.errorCode));
+    }
+
+    /**
+     * Retrieve the associated error code
+     *
+     * @hide
+     */
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /**
+     * Retrieve a developer-readable diagnostic information string
+     * associated with the exception. Do not show this to end-users,
+     * since this string will not be localized or generally comprehensible
+     * to end-users.
+     */
+    @NonNull
+    public String getDiagnosticInfo() {
+        return mDiagnosticInfo;
+    }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 13a22b4..e628d18 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1878,9 +1878,7 @@
      * @param flags   Specify {@link #CONFIGURE_FLAG_ENCODE} to configure the
      *                component as an encoder.
      * @param descrambler Specify a descrambler object to facilitate secure
-     *                descrambling of the media data. descrambler must not be
-     *                null if this method is used. For non-secure codecs, use
-     *                {@link #configure} and with null crypto parameter.
+     *                descrambling of the media data, or null for non-secure codecs.
      * @throws IllegalArgumentException if the surface has been released (or is invalid),
      * or the format is unacceptable (e.g. missing a mandatory key),
      * or the flags are not set properly
@@ -1891,8 +1889,9 @@
      */
     public void configure(
             @Nullable MediaFormat format, @Nullable Surface surface,
-            @ConfigureFlag int flags, @NonNull MediaDescrambler descrambler) {
-        configure(format, surface, null, descrambler.getBinder(), flags);
+            @ConfigureFlag int flags, @Nullable MediaDescrambler descrambler) {
+        configure(format, surface, null,
+                descrambler != null ? descrambler.getBinder() : null, flags);
     }
 
     private void configure(
diff --git a/media/java/android/media/MediaDescrambler.java b/media/java/android/media/MediaDescrambler.java
index f5eede8..2dd1097 100644
--- a/media/java/android/media/MediaDescrambler.java
+++ b/media/java/android/media/MediaDescrambler.java
@@ -17,10 +17,12 @@
 package android.media;
 
 import android.annotation.NonNull;
+import android.media.MediaCasException.UnsupportedCasException;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 
 import java.nio.ByteBuffer;
@@ -54,13 +56,13 @@
     /**
      * Class for parceling descrambling parameters over IDescrambler binder.
      */
+    // This class currently is not used by Java binder. descramble() goes through
+    // jni to use shared memory. However, the parcelable is still required for AIDL.
     static class DescrambleInfo implements Parcelable {
         private DescrambleInfo() {
-            // TODO: implement
         }
 
         private DescrambleInfo(Parcel in) {
-            // TODO: disable
         }
 
         @Override
@@ -70,7 +72,6 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            // TODO: implement
         }
 
         public static final Parcelable.Creator<DescrambleInfo> CREATOR
@@ -112,13 +113,6 @@
         return mIDescrambler.asBinder();
     }
 
-    /*
-     * TODO: handle ServiceSpecificException from the mIDescrambler
-     * All Drm-specific failures will be thrown by mIDescrambler as
-     * ServiceSpecificException exception with Drm error code.
-     * These need to be re-thrown as crypto exceptions.
-     */
-
     /**
      * Query if the scrambling scheme requires the use of a secure decoder
      * to decode data of the given mime type.
@@ -150,14 +144,16 @@
      * @param sessionId the MediaCas sessionId to associate with this
      * MediaDescrambler instance.
      *
-     * @throws IllegalStateException if the descrambler instance is not valid,
-     * or IllegalArgumentException if the sessionId is not valid.
+     * @throws IllegalStateException if the descrambler instance is not valid.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
     public final void setMediaCasSession(@NonNull byte[] sessionId) {
         validateInternalStates();
 
         try {
             mIDescrambler.setMediaCasSession(sessionId);
+        } catch (ServiceSpecificException e) {
+            MediaCasStateException.throwExceptions(e);
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
         }
@@ -179,9 +175,7 @@
      * values indicating errors.
      *
      * @throws IllegalStateException if the descrambler instance is not valid.
-     */
-    /*
-     * TODO: throw DRM-specific exception if decrambling is failing.
+     * @throws MediaCasStateException for CAS-specific state exceptions.
      */
     public final int descramble(
             @NonNull ByteBuffer srcBuf, int srcPos, ByteBuffer dstBuf, int dstPos,
@@ -208,12 +202,17 @@
                     "Invalid CryptoInfo: key array is invalid!");
         }
 
-        return native_descramble(
-                cryptoInfo.key[0],
-                cryptoInfo.numSubSamples,
-                cryptoInfo.numBytesOfClearData,
-                cryptoInfo.numBytesOfEncryptedData,
-                srcBuf, srcPos, dstBuf, dstPos);
+        try {
+            return native_descramble(
+                    cryptoInfo.key[0],
+                    cryptoInfo.numSubSamples,
+                    cryptoInfo.numBytesOfClearData,
+                    cryptoInfo.numBytesOfEncryptedData,
+                    srcBuf, srcPos, dstBuf, dstPos);
+        } catch (ServiceSpecificException e) {
+            MediaCasStateException.throwExceptions(e);
+        }
+        return -1;
     }
 
     public final void release() {
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index a84cee8..44494a6 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -141,6 +141,11 @@
             mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
             mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
             mDefaultAudioVideo.updatePresentationDisplay();
+            if (((AudioManager) appContext.getSystemService(Context.AUDIO_SERVICE))
+                    .isVolumeFixed()) {
+                mDefaultAudioVideo.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
+            }
+
             addRouteStatic(mDefaultAudioVideo);
 
             // This will select the active wifi display route if there is one.
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 7122eaf..5bf205e 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -87,10 +87,11 @@
      */
     public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
 
-    private static final int CONNECT_STATE_DISCONNECTED = 0;
-    private static final int CONNECT_STATE_CONNECTING = 1;
-    private static final int CONNECT_STATE_CONNECTED = 2;
-    private static final int CONNECT_STATE_SUSPENDED = 3;
+    private static final int CONNECT_STATE_DISCONNECTING = 0;
+    private static final int CONNECT_STATE_DISCONNECTED = 1;
+    private static final int CONNECT_STATE_CONNECTING = 2;
+    private static final int CONNECT_STATE_CONNECTED = 3;
+    private static final int CONNECT_STATE_SUSPENDED = 4;
 
     private final Context mContext;
     private final ComponentName mServiceComponent;
@@ -213,6 +214,7 @@
         // It's ok to call this any state, because allowing this lets apps not have
         // to check isConnected() unnecessarily. They won't appreciate the extra
         // assertions for this. We do everything we can here to go back to a sane state.
+        mState = CONNECT_STATE_DISCONNECTING;
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -535,7 +537,7 @@
 
         // If we are connected, tell the service that we are watching. If we aren't connected,
         // the service will be told when we connect.
-        if (mState == CONNECT_STATE_CONNECTED) {
+        if (isConnected()) {
             try {
                 if (options == null) {
                     mServiceBinder.addSubscriptionDeprecated(parentId, mServiceCallbacks);
@@ -563,7 +565,7 @@
         // Tell the service if necessary.
         try {
             if (callback == null) {
-                if (mState == CONNECT_STATE_CONNECTED) {
+                if (isConnected()) {
                     mServiceBinder.removeSubscriptionDeprecated(parentId, mServiceCallbacks);
                     mServiceBinder.removeSubscription(parentId, null, mServiceCallbacks);
                 }
@@ -572,7 +574,7 @@
                 final List<Bundle> optionsList = sub.getOptionsList();
                 for (int i = callbacks.size() - 1; i >= 0; --i) {
                     if (callbacks.get(i) == callback) {
-                        if (mState == CONNECT_STATE_CONNECTED) {
+                        if (isConnected()) {
                             mServiceBinder.removeSubscription(
                                     parentId, callback.mToken, mServiceCallbacks);
                         }
@@ -597,6 +599,8 @@
      */
     private static String getStateLabel(int state) {
         switch (state) {
+            case CONNECT_STATE_DISCONNECTING:
+                return "CONNECT_STATE_DISCONNECTING";
             case CONNECT_STATE_DISCONNECTED:
                 return "CONNECT_STATE_DISCONNECTED";
             case CONNECT_STATE_CONNECTING:
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index bee3f52..f10f442 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -77,13 +77,19 @@
     /**
      * Set this flag on the session to indicate that it can handle media button
      * events.
+     * @deprecated This flag is no longer used. All media sessions are expected to handle media
+     * button events now.
      */
+    @Deprecated
     public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
 
     /**
      * Set this flag on the session to indicate that it handles transport
      * control commands through its {@link Callback}.
+     * @deprecated This flag is no longer used. All media sessions are expected to handle transport
+     * controls now.
      */
+    @Deprecated
     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
 
     /**
diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp
index 7585664..f031dbb 100644
--- a/media/jni/android_media_MediaDescrambler.cpp
+++ b/media/jni/android_media_MediaDescrambler.cpp
@@ -129,7 +129,7 @@
     mMem = mDealer->allocate(neededSize);
 }
 
-ssize_t JDescrambler::descramble(
+Status JDescrambler::descramble(
         jbyte key,
         size_t numSubSamples,
         ssize_t totalLength,
@@ -137,7 +137,8 @@
         const void *srcPtr,
         jint srcOffset,
         void *dstPtr,
-        jint dstOffset) {
+        jint dstOffset,
+        ssize_t *result) {
     // TODO: IDescrambler::descramble() is re-entrant, however because we
     // only have 1 shared mem buffer, we can only do 1 descramble at a time.
     // Concurrency might be improved by allowing on-demand allocation of up
@@ -159,16 +160,16 @@
     info.dstPtr = NULL;
     info.dstOffset = 0;
 
-    int32_t result;
-    binder::Status status = mDescrambler->descramble(info, &result);
+    int32_t descrambleResult;
+    Status status = mDescrambler->descramble(info, &descrambleResult);
 
-    if (!status.isOk() || result > totalLength) {
-        return -1;
+    if (status.isOk()) {
+        *result = (descrambleResult <= totalLength) ? descrambleResult : -1;
+        if (*result > 0) {
+            memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *result);
+        }
     }
-    if (result > 0) {
-        memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), result);
-    }
-    return result;
+    return status;
 }
 
 }  // namespace android
@@ -251,11 +252,45 @@
         numBytesOfClearData = NULL;
     }
 
+    if (totalSize < 0) {
+        delete[] subSamples;
+        return -1;
+    }
+
     *outSubSamples = subSamples;
 
     return totalSize;
 }
 
+static jthrowable createServiceSpecificException(
+        JNIEnv *env, int serviceSpecificError, const char *msg) {
+    if (env->ExceptionCheck()) {
+        ALOGW("Discarding pending exception");
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+    }
+
+    ScopedLocalRef<jclass> clazz(
+            env, env->FindClass("android/os/ServiceSpecificException"));
+    CHECK(clazz.get() != NULL);
+
+    const jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V");
+    CHECK(ctor != NULL);
+
+    ScopedLocalRef<jstring> msgObj(
+            env, env->NewStringUTF(msg != NULL ?
+                    msg : String8::format("Error %#x", serviceSpecificError)));
+
+    return (jthrowable)env->NewObject(
+            clazz.get(), ctor, serviceSpecificError, msgObj.get());
+}
+
+static void throwServiceSpecificException(
+        JNIEnv *env, int serviceSpecificError, const char *msg) {
+    jthrowable exception = createServiceSpecificException(env, serviceSpecificError, msg);
+    env->Throw(exception);
+}
+
 static jint android_media_MediaDescrambler_native_descramble(
         JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
         jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
@@ -290,11 +325,11 @@
                     env, dstBuf, dstOffset, totalLength, &dstPtr, &dstArray);
         }
     }
-
+    Status status;
     if (err == OK) {
-        result = descrambler->descramble(
+        status = descrambler->descramble(
                 key, numSubSamples, totalLength, subSamples,
-                srcPtr, srcOffset, dstPtr, dstOffset);
+                srcPtr, srcOffset, dstPtr, dstOffset, &result);
     }
 
     delete[] subSamples;
@@ -304,6 +339,51 @@
     if (dstArray != NULL) {
         env->ReleaseByteArrayElements(dstArray, (jbyte *)dstPtr, 0);
     }
+
+    if (!status.isOk()) {
+        switch (status.exceptionCode()) {
+            case Status::EX_SECURITY:
+                jniThrowException(env, "java/lang/SecurityException",
+                        status.exceptionMessage());
+                break;
+            case Status::EX_BAD_PARCELABLE:
+                jniThrowException(env, "java/lang/BadParcelableException",
+                        status.exceptionMessage());
+                break;
+            case Status::EX_ILLEGAL_ARGUMENT:
+                jniThrowException(env, "java/lang/IllegalArgumentException",
+                        status.exceptionMessage());
+                break;
+            case Status::EX_NULL_POINTER:
+                jniThrowException(env, "java/lang/NullPointerException",
+                        status.exceptionMessage());
+                break;
+            case Status::EX_ILLEGAL_STATE:
+                jniThrowException(env, "java/lang/IllegalStateException",
+                        status.exceptionMessage());
+                break;
+            case Status::EX_NETWORK_MAIN_THREAD:
+                jniThrowException(env, "java/lang/NetworkOnMainThreadException",
+                        status.exceptionMessage());
+                break;
+            case Status::EX_UNSUPPORTED_OPERATION:
+                jniThrowException(env, "java/lang/UnsupportedOperationException",
+                        status.exceptionMessage());
+                break;
+            case Status::EX_SERVICE_SPECIFIC:
+                throwServiceSpecificException(env, status.serviceSpecificErrorCode(),
+                        status.exceptionMessage());
+                break;
+            default:
+            {
+                String8 msg;
+                msg.appendFormat("Unknown exception code: %d, msg: %s",
+                        status.exceptionCode(), status.exceptionMessage().string());
+                jniThrowException(env, "java/lang/RuntimeException", msg.string());
+                break;
+            }
+        }
+    }
     return result;
 }
 
diff --git a/media/jni/android_media_MediaDescrambler.h b/media/jni/android_media_MediaDescrambler.h
index e944a90..aeef05e 100644
--- a/media/jni/android_media_MediaDescrambler.h
+++ b/media/jni/android_media_MediaDescrambler.h
@@ -19,6 +19,7 @@
 
 #include "jni.h"
 
+#include <binder/Status.h>
 #include <media/cas/DescramblerAPI.h>
 #include <media/stagefright/foundation/ABase.h>
 #include <utils/Mutex.h>
@@ -31,11 +32,12 @@
 class IDescrambler;
 };
 using namespace media;
+using binder::Status;
 
 struct JDescrambler : public RefBase {
     JDescrambler(JNIEnv *env, jobject descramberBinderObj);
 
-    ssize_t descramble(
+    Status descramble(
             jbyte key,
             size_t numSubSamples,
             ssize_t totalLength,
@@ -43,7 +45,8 @@
             const void *srcPtr,
             jint srcOffset,
             void *dstPtr,
-            jint dstOffset);
+            jint dstOffset,
+            ssize_t *result);
 
 protected:
     virtual ~JDescrambler();
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/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 8df194c..e450283 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -27,7 +27,9 @@
     <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
 
-    <application android:label="@string/app_name" >
+    <application
+        android:label="@string/app_name"
+        android:directBootAware="true">
         <receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver">
             <intent-filter>
                 <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index e49463f..1b6aca1 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -58,6 +58,7 @@
 import android.widget.ArrayAdapter;
 import android.widget.TextView;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.Preconditions;
 
@@ -180,15 +181,20 @@
     }
 
     private void startDiscovery(AssociationRequest request) {
-        mRequest = request;
+        if (!request.equals(mRequest)) {
+            mRequest = request;
 
-        mFilters = request.getDeviceFilters();
-        mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
-        mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
-        mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
-        mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
+            mFilters = request.getDeviceFilters();
+            mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
+            mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
+            mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
+            mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
 
-        reset();
+            reset();
+        }
+        if (!ArrayUtils.isEmpty(mDevicesFound)) {
+            onReadyToShowUI();
+        }
 
         if (shouldScan(mBluetoothFilters)) {
             final IntentFilter intentFilter = new IntentFilter();
@@ -228,10 +234,18 @@
 
     private void stopScan() {
         if (DEBUG) Log.i(LOG_TAG, "stopScan() called");
-        mBluetoothAdapter.cancelDiscovery();
-        mBLEScanner.stopScan(mBLEScanCallback);
-        unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
-        unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);
+
+        if (shouldScan(mBluetoothFilters)) {
+            mBluetoothAdapter.cancelDiscovery();
+            unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
+        }
+        if (shouldScan(mBLEFilters)) {
+            mBLEScanner.stopScan(mBLEScanCallback);
+        }
+        if (shouldScan(mWifiFilters)) {
+            unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);
+        }
+
         stopSelf();
     }
 
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8802010..b60e2fe 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -480,21 +480,18 @@
     }
 
     @Override
-    public boolean ejectRoot(String rootId) {
+    public void ejectRoot(String rootId) {
         final long token = Binder.clearCallingIdentity();
-        boolean ejected = false;
         RootInfo root = mRoots.get(rootId);
         if (root != null) {
             try {
                 mStorageManager.unmount(root.volumeId);
-                ejected = true;
             } catch (RuntimeException e) {
-                Log.w(TAG, "Root '" + root.title + "' could not be ejected");
+                throw new IllegalStateException(e);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
-        return ejected;
     }
 
     @Override
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ca34bb9..cc2dc1b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -356,7 +356,7 @@
     </string-array>
 
    <!-- Title for profile selection dialog [CHAR LIMIT=30] -->
-   <string name="choose_profile">Choose Profile</string>
+   <string name="choose_profile">Choose profile</string>
 
     <!-- Header for items under the personal user [CHAR LIMIT=30] -->
     <string name="category_personal">Personal</string>
@@ -850,9 +850,9 @@
     <string name="disabled_by_admin_summary_text">Controlled by admin</string>
 
     <!-- Summary for switch preference to denote it is switched on [CHAR LIMIT=50] -->
-    <string name="enabled_by_admin">Enabled by administrator</string>
+    <string name="enabled_by_admin">Enabled by admin</string>
     <!-- Summary for switch preference to denote it is switched off [CHAR LIMIT=50] -->
-    <string name="disabled_by_admin">Disabled by administrator</string>
+    <string name="disabled_by_admin">Disabled by admin</string>
 
     <!-- Option in navigation drawer that leads to Settings main screen [CHAR LIMIT=30] -->
     <string name="home">Settings Home</string>
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..1f86f8b 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);
@@ -525,9 +526,13 @@
                         // the given ScanResult.  This is used for showing that a given AP
                         // (ScanResult) is available via a Passpoint provider (provider friendly
                         // name).
-                        WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
-                        if (config != null) {
-                            accessPoint.update(config);
+                        try {
+                            WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
+                            if (config != null) {
+                                accessPoint.update(config);
+                            }
+                        } catch (UnsupportedOperationException e) {
+                            // Passpoint not supported on the device.
                         }
                     }
 
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/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1f1c189..1a752f9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2037,6 +2037,8 @@
 
         private final BackupManager mBackupManager;
 
+        private String mSettingsCreationBuildId;
+
         public SettingsRegistry() {
             mHandler = new MyHandler(getContext().getMainLooper());
             mGenerationRegistry = new GenerationRegistry(mLock);
@@ -2502,6 +2504,8 @@
                     return;
                 }
 
+                mSettingsCreationBuildId = Build.ID;
+
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     List<UserInfo> users = mUserManager.getUsers(true);
@@ -2570,6 +2574,12 @@
                 ensureSettingsStateLocked(globalKey);
                 SettingsState globalSettings = mSettingsStates.get(globalKey);
                 migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL);
+                // If this was just created
+                if (mSettingsCreationBuildId != null) {
+                    globalSettings.insertSettingLocked(Settings.Global.DATABASE_CREATION_BUILDID,
+                            mSettingsCreationBuildId, null, true,
+                            SettingsState.SYSTEM_PACKAGE_NAME);
+                }
                 globalSettings.persistSyncLocked();
             }
 
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 1c49a55d..f6f2935 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -31,8 +31,12 @@
     <!-- Text of notification indicating that bugreport will appear on the phone. [CHAR LIMIT=100] -->
     <string name="bugreport_finished_text" product="watch">The bug report will appear on the phone shortly</string>
     <!-- Text of notification indicating that tapping will share the captured bugreport. [CHAR LIMIT=100] -->
+    <string name="bugreport_finished_text" product="tv">Select to share your bug report</string>
+    <!-- Text of notification indicating that tapping will share the captured bugreport. [CHAR LIMIT=100] -->
     <string name="bugreport_finished_text" product="default">Tap to share your bug report</string>
     <!-- Text of notification indicating that swipe left will share the captured bugreport, but giving user the option to wait for the screenshot. [CHAR LIMIT=100] -->
+    <string name="bugreport_finished_pending_screenshot_text" product="tv">Select to share your bug report without a screenshot or wait for the screenshot to finish</string>
+    <!-- Text of notification indicating that swipe left will share the captured bugreport, but giving user the option to wait for the screenshot. [CHAR LIMIT=100] -->
     <string name="bugreport_finished_pending_screenshot_text" product="watch">Tap to share your bug report without a screenshot or wait for the screenshot to finish</string>
     <!-- Text of notification indicating that tapping will share the captured bugreport, but giving user the option to wait for the screenshot. [CHAR LIMIT=100] -->
     <string name="bugreport_finished_pending_screenshot_text" product="default">Tap to share your bug report without a screenshot or wait for the screenshot to finish</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4b932a3..e4d71b6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -531,7 +531,7 @@
             android:excludeFromRecents="true"
             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SHOW_BRIGHTNESS_DIALOG" />
+                <action android:name="com.android.intent.action.SHOW_BRIGHTNESS_DIALOG" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
@@ -564,8 +564,8 @@
         <receiver
             android:name=".statusbar.KeyboardShortcutsReceiver">
             <intent-filter>
-                <action android:name="android.intent.action.DISMISS_KEYBOARD_SHORTCUTS" />
-                <action android:name="android.intent.action.SHOW_KEYBOARD_SHORTCUTS" />
+                <action android:name="com.android.intent.action.DISMISS_KEYBOARD_SHORTCUTS" />
+                <action android:name="com.android.intent.action.SHOW_KEYBOARD_SHORTCUTS" />
             </intent-filter>
         </receiver>
     </application>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java
new file mode 100644
index 0000000..9e5db73
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java
@@ -0,0 +1,41 @@
+/*
+ * 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.plugins;
+
+import com.android.systemui.plugins.VolumeDialog.Callback;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * This interface is really just a stub for initialization/teardown, actual handling of
+ * when to show will be done through {@link VolumeDialogController}
+ */
+@ProvidesInterface(action = VolumeDialog.ACTION, version = VolumeDialog.VERSION)
+@DependsOn(target = Callback.class)
+public interface VolumeDialog extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_VOLUME";
+    int VERSION = 1;
+
+    void init(int windowType, Callback callback);
+    void destroy();
+
+    @ProvidesInterface(version = VERSION)
+    public interface Callback {
+        int VERSION = 1;
+
+        void onZenSettingsClicked();
+        void onZenPrioritySettingsClicked();
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
new file mode 100644
index 0000000..903ff72
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -0,0 +1,176 @@
+/*
+ * 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.plugins;
+
+import android.annotation.IntegerRes;
+import android.content.ComponentName;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.systemui.plugins.VolumeDialogController.Callbacks;
+import com.android.systemui.plugins.VolumeDialogController.State;
+import com.android.systemui.plugins.VolumeDialogController.StreamState;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Manages the VolumeDialog.
+ *
+ * Accessible through {@link PluginDependency}
+ */
+@ProvidesInterface(version = VolumeDialogController.VERSION)
+@DependsOn(target = StreamState.class)
+@DependsOn(target = State.class)
+@DependsOn(target = Callbacks.class)
+public interface VolumeDialogController {
+    int VERSION = 1;
+
+    void setActiveStream(int stream);
+    void setStreamVolume(int stream, int userLevel);
+    void setRingerMode(int ringerModeNormal, boolean external);
+
+    boolean hasVibrator();
+    void vibrate();
+
+    AudioManager getAudioManager();
+
+    void notifyVisible(boolean visible);
+
+    void addCallback(Callbacks callbacks, Handler handler);
+    void removeCallback(Callbacks callbacks);
+
+    void userActivity();
+    void getState();
+
+    @ProvidesInterface(version = StreamState.VERSION)
+    public static final class StreamState {
+        public static final int VERSION = 1;
+
+        public boolean dynamic;
+        public int level;
+        public int levelMin;
+        public int levelMax;
+        public boolean muted;
+        public boolean muteSupported;
+        public @IntegerRes int name;
+        public String remoteLabel;
+        public boolean routedToBluetooth;
+
+        public StreamState copy() {
+            final StreamState rt = new StreamState();
+            rt.dynamic = dynamic;
+            rt.level = level;
+            rt.levelMin = levelMin;
+            rt.levelMax = levelMax;
+            rt.muted = muted;
+            rt.muteSupported = muteSupported;
+            rt.name = name;
+            rt.remoteLabel = remoteLabel;
+            rt.routedToBluetooth = routedToBluetooth;
+            return rt;
+        }
+    }
+
+    @ProvidesInterface(version = State.VERSION)
+    public static final class State {
+        public static final int VERSION = 1;
+
+        public static int NO_ACTIVE_STREAM = -1;
+
+        public final SparseArray<StreamState> states = new SparseArray<>();
+
+        public int ringerModeInternal;
+        public int ringerModeExternal;
+        public int zenMode;
+        public ComponentName effectsSuppressor;
+        public String effectsSuppressorName;
+        public int activeStream = NO_ACTIVE_STREAM;
+
+        public State copy() {
+            final State rt = new State();
+            for (int i = 0; i < states.size(); i++) {
+                rt.states.put(states.keyAt(i), states.valueAt(i).copy());
+            }
+            rt.ringerModeExternal = ringerModeExternal;
+            rt.ringerModeInternal = ringerModeInternal;
+            rt.zenMode = zenMode;
+            if (effectsSuppressor != null) {
+                rt.effectsSuppressor = effectsSuppressor.clone();
+            }
+            rt.effectsSuppressorName = effectsSuppressorName;
+            rt.activeStream = activeStream;
+            return rt;
+        }
+
+        @Override
+        public String toString() {
+            return toString(0);
+        }
+
+        public String toString(int indent) {
+            final StringBuilder sb = new StringBuilder("{");
+            if (indent > 0) sep(sb, indent);
+            for (int i = 0; i < states.size(); i++) {
+                if (i > 0) {
+                    sep(sb, indent);
+                }
+                final int stream = states.keyAt(i);
+                final StreamState ss = states.valueAt(i);
+                sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level)
+                        .append('[').append(ss.levelMin).append("..").append(ss.levelMax)
+                        .append(']');
+                if (ss.muted) sb.append(" [MUTED]");
+                if (ss.dynamic) sb.append(" [DYNAMIC]");
+            }
+            sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal);
+            sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal);
+            sep(sb, indent); sb.append("zenMode:").append(zenMode);
+            sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor);
+            sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
+            sep(sb, indent); sb.append("activeStream:").append(activeStream);
+            if (indent > 0) sep(sb, indent);
+            return sb.append('}').toString();
+        }
+
+        private static void sep(StringBuilder sb, int indent) {
+            if (indent > 0) {
+                sb.append('\n');
+                for (int i = 0; i < indent; i++) {
+                    sb.append(' ');
+                }
+            } else {
+                sb.append(',');
+            }
+        }
+    }
+
+    @ProvidesInterface(version = Callbacks.VERSION)
+    public interface Callbacks {
+        int VERSION = 1;
+
+        void onShowRequested(int reason);
+        void onDismissRequested(int reason);
+        void onStateChanged(State state);
+        void onLayoutDirectionChanged(int layoutDirection);
+        void onConfigurationChanged();
+        void onShowVibrateHint();
+        void onShowSilentHint();
+        void onScreenOff();
+        void onShowSafetyWarning(int flags);
+        void onAccessibilityModeChanged(Boolean showA11yStream);
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
new file mode 100644
index 0000000..8dde357
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -0,0 +1,97 @@
+/*
+ * 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.plugins.statusbar;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+
+@ProvidesInterface(action = NotificationMenuRowPlugin.ACTION,
+        version = NotificationMenuRowPlugin.VERSION)
+@DependsOn(target = OnMenuEventListener.class)
+@DependsOn(target = MenuItem.class)
+@DependsOn(target = NotificationSwipeActionHelper.class)
+@DependsOn(target = SnoozeOption.class)
+public interface NotificationMenuRowPlugin extends Plugin {
+
+    public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
+    public static final int VERSION = 1;
+
+    @ProvidesInterface(version = OnMenuEventListener.VERSION)
+    public interface OnMenuEventListener {
+        public static final int VERSION = 1;
+        public void onMenuClicked(View row, int x, int y, MenuItem menu);
+
+        public void onMenuReset(View row);
+
+        public void onMenuShown(View row);
+    }
+
+    @ProvidesInterface(version = MenuItem.VERSION)
+    public interface MenuItem {
+        public static final int VERSION = 1;
+        public View getMenuView();
+
+        public View getGutsView();
+
+        public String getContentDescription();
+    }
+
+    /**
+     * @return a list of items to populate the menu 'behind' a notification.
+     */
+    public ArrayList<MenuItem> getMenuItems(Context context);
+
+    /**
+     * @return the {@link MenuItem} to display when a notification is long pressed.
+     */
+    public MenuItem getLongpressMenuItem(Context context);
+
+    public void setMenuItems(ArrayList<MenuItem> items);
+
+    public void setMenuClickListener(OnMenuEventListener listener);
+
+    public void setSwipeActionHelper(NotificationSwipeActionHelper listener);
+
+    public void setAppName(String appName);
+
+    public void createMenu(ViewGroup parent);
+
+    public View getMenuView();
+
+    public boolean isMenuVisible();
+
+    public void resetMenu();
+
+    public void onTranslationUpdate(float translation);
+
+    public void onHeightUpdate();
+
+    public boolean onTouchEvent(View view, MotionEvent ev, float velocity);
+
+    public default boolean useDefaultMenuItems() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
deleted file mode 100644
index 529c421..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
+++ /dev/null
@@ -1,95 +0,0 @@
-
-package com.android.systemui.plugins.statusbar;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.service.notification.SnoozeCriterion;
-import android.service.notification.StatusBarNotification;
-import android.view.View;
-
-import java.util.ArrayList;
-
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-@ProvidesInterface(action = NotificationMenuRowProvider.ACTION,
-        version = NotificationMenuRowProvider.VERSION)
-public interface NotificationMenuRowProvider extends Plugin {
-
-    public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
-
-    public static final int VERSION = 1;
-
-    /**
-     * Returns a list of items to populate the menu 'behind' a notification.
-     */
-    public ArrayList<MenuItem> getMenuItems(Context context);
-
-    public interface OnMenuClickListener {
-        public void onMenuClicked(View row, int x, int y, MenuItem menu);
-
-        public void onMenuReset(View row);
-    }
-
-    public interface GutsInteractionListener {
-        public void onInteraction(View view);
-
-        public void closeGuts(View view);
-    }
-
-    public interface GutsContent {
-        public void setInteractionListener(GutsInteractionListener listener);
-
-        public View getContentView();
-
-        public boolean handleCloseControls(boolean save);
-
-        public boolean willBeRemoved();
-    }
-
-    public interface SnoozeGutsContent extends GutsContent {
-        public void setSnoozeListener(SnoozeListener listener);
-
-        public void setStatusBarNotification(StatusBarNotification sbn);
-    }
-
-    public interface SnoozeListener {
-        public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption);
-    }
-
-    public static class MenuItem {
-        public Drawable icon;
-        public String menuDescription;
-        public View menuView;
-        public GutsContent gutsContent;
-
-        public MenuItem(Drawable i, String s, GutsContent content) {
-            icon = i;
-            menuDescription = s;
-            gutsContent = content;
-        }
-
-        public View getGutsView() {
-            return gutsContent.getContentView();
-        }
-
-        public boolean onTouch(View v, int x, int y) {
-            return false;
-        }
-    }
-
-    public static class SnoozeOption {
-        public SnoozeCriterion criterion;
-        public int snoozeForMinutes;
-        public CharSequence description;
-        public CharSequence confirmation;
-
-        public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc,
-                CharSequence confirm) {
-            criterion = crit;
-            snoozeForMinutes = minsToSnoozeFor;
-            description = desc;
-            confirmation = confirm;
-        }
-    }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
new file mode 100644
index 0000000..4ce1e36
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -0,0 +1,72 @@
+/*
+ * 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.plugins.statusbar;
+
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+import android.view.MotionEvent;
+import android.view.View;
+
+@ProvidesInterface(version = NotificationSwipeActionHelper.VERSION)
+@DependsOn(target = SnoozeOption.class)
+public interface NotificationSwipeActionHelper {
+    public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_SWIPE_ACTION";
+
+    public static final int VERSION = 1;
+
+    /**
+     * Call this to dismiss a notification.
+     */
+    public void dismiss(View animView, float velocity);
+
+    /**
+     * Call this to snap a notification to provided {@code targetLeft}.
+     */
+    public void snap(View animView, float velocity, float targetLeft);
+
+    /**
+     * Call this to snooze a notification based on the provided {@link SnoozeOption}.
+     */
+    public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+
+    public float getMinDismissVelocity();
+
+    public boolean isDismissGesture(MotionEvent ev);
+
+    public boolean swipedFarEnough(float translation, float viewSize);
+
+    public boolean swipedFastEnough(float translation, float velocity);
+
+    @ProvidesInterface(version = SnoozeOption.VERSION)
+    public static class SnoozeOption {
+        public static final int VERSION = 1;
+        public int snoozeForMinutes;
+        public SnoozeCriterion criterion;
+        public CharSequence description;
+        public CharSequence confirmation;
+
+        public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc,
+                CharSequence confirm) {
+            criterion = crit;
+            snoozeForMinutes = minsToSnoozeFor;
+            description = desc;
+            confirmation = confirm;
+        }
+    }
+}
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index ff689aa..2f52227 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -327,7 +327,7 @@
 
     <!-- An explanation text that the credential needs to be entered because a device admin has
     locked the device. [CHAR LIMIT=80] -->
-    <string name="kg_prompt_reason_device_admin">Device administrator locked device</string>
+    <string name="kg_prompt_reason_device_admin">Device locked by admin</string>
 
     <!-- An explanation text that the credential needs to be entered because the user has clicked
      the force lock button. [CHAR LIMIT=80] -->
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/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index fc1271c..e3063c5 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -85,6 +85,7 @@
         android:layout_height="@dimen/keyguard_affordance_height"
         android:layout_gravity="bottom|center_horizontal"
         android:src="@drawable/ic_lock_24dp"
+        android:contentDescription="@string/accessibility_unlock_button"
         android:scaleType="center" />
 
 </com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
diff --git a/packages/SystemUI/res/layout/notification_menu_row.xml b/packages/SystemUI/res/layout/notification_menu_row.xml
deleted file mode 100644
index 12bcf81..0000000
--- a/packages/SystemUI/res/layout/notification_menu_row.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright 2016, The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<com.android.systemui.statusbar.NotificationMenuRow
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:visibility="invisible"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 0fd2dc9..64caefd 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -14,7 +14,7 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<RelativeLayout
+<com.android.systemui.qs.tileimpl.ButtonRelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -88,4 +88,4 @@
         android:alpha="?android:attr/disabledAlpha"
         android:background="?android:attr/colorForeground"/>
 
-</RelativeLayout>
+</com.android.systemui.qs.tileimpl.ButtonRelativeLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index d62cc18..7f37087 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -23,13 +23,7 @@
     android:clickable="true"
     >
 
-    <ViewStub
-        android:layout="@layout/notification_menu_row"
-        android:id="@+id/menu_row_stub"
-        android:inflatedId="@+id/notification_menu_row"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        />
+    <!-- Menu displayed behind notification added here programmatically -->
 
     <com.android.systemui.statusbar.NotificationBackgroundView
         android:id="@+id/backgroundNormal"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d6d01d8a..bf17e38 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -190,7 +190,8 @@
     <!-- Notification text displayed when we fail to save a screenshot. [CHAR LIMIT=100] -->
     <string name="screenshot_failed_to_save_text">Can\'t save screenshot due to limited storage space.</string>
     <!-- Notification text displayed when we fail to take a screenshot. [CHAR LIMIT=100] -->
-    <string name="screenshot_failed_to_capture_text">Taking screenshots is not allowed by the app or your organization.</string>
+    <string name="screenshot_failed_to_capture_text">Taking screenshots isn\'t allowed by the app or
+        your organization</string>
 
     <!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] -->
     <string name="usb_preference_title">USB file transfer options</string>
@@ -221,8 +222,8 @@
     <string name="accessibility_voice_assist_button">Voice Assist</string>
     <!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_unlock_button">Unlock</string>
-    <!-- Content description of the unlock button when fingerpint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_unlock_button_fingerprint">Unlock button, waiting for fingerprint</string>
+    <!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_waiting_for_fingerprint">Waiting for fingerprint</string>
     <!-- Accessibility action of the unlock button when fingerpint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_unlock_without_fingerprint">Unlock without using your fingerprint</string>
     <!-- Click action label for accessibility for the unlock button. [CHAR LIMIT=NONE] -->
@@ -1020,6 +1021,51 @@
     <!-- Footer vpn present text [CHAR LIMIT=50] -->
     <string name="branded_vpn_footer">Network may be monitored</string>
 
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner that can monitor the network traffic [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_management_monitoring">Your organization manages this device and may monitor network traffic</string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner that can monitor the network traffic [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_management_monitoring"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> manages this device and may monitor network traffic</string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to a VPN [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_management_named_vpn">Device is managed by your organization and connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to a VPN [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_management_named_vpn">Device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> and connected to <xliff:g id="vpn_app" example="Foo VPN App">%2$s</xliff:g></string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_management">Device is managed by your organization</string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_management">Device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g></string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to two VPNs, one in the current user, one in the managed profile [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_management_vpns">Device is managed by your organization and connected to VPNs</string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a device owner and is connected to two VPNs, one in the current user, one in the managed profile [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_management_vpns">Device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> and connected to VPNs</string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a managed profile which can be monitored by the profile owner [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_managed_profile_monitoring">Your organization may monitor network traffic in your work profile</string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a managed profile which can be monitored by the profile owner [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_managed_profile_monitoring"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> may monitor network traffic in your work profile</string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that a certificate authorithy is installed on this device and the traffic might be monitored [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_monitoring">Network may be monitored</string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to two VPNs, one in the current user, one in the managed profile [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_vpns">Device connected to VPNs</string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN in the work profile [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_managed_profile_named_vpn">Work profile connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN in the personal profile (as opposed to the work profile) [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_personal_profile_named_vpn">Personal profile connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+
+    <!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN [CHAR LIMIT=100] -->
+    <string name="quick_settings_disclosure_named_vpn">Device connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+
     <!-- Monitoring dialog title for device owned devices [CHAR LIMIT=35] -->
     <string name="monitoring_title_device_owned">Device monitoring</string>
 
@@ -1034,7 +1080,7 @@
     <string name="monitoring_subtitle_vpn">VPN</string>
 
     <!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=35]-->
-    <string name="monitoring_subtitle_network_logging">Network Logging</string>
+    <string name="monitoring_subtitle_network_logging">Network logging</string>
 
     <!-- Monitoring dialog subtitle for the section describing certificate authorities [CHAR LIMIT=35]-->
     <string name="monitoring_subtitle_ca_certificate">CA certificates</string>
@@ -1064,7 +1110,7 @@
     <string name="monitoring_description_ca_certificate">A certificate authority is installed on this device. Your secure network traffic may be monitored or modified.</string>
 
     <!-- Monitoring dialog: Description of Network Logging. [CHAR LIMIT=NONE]-->
-    <string name="monitoring_description_management_network_logging">Your admin has turned on network logging, which monitores traffic on your device.</string>
+    <string name="monitoring_description_management_network_logging">Your admin has turned on network logging, which monitors traffic on your device.</string>
 
     <!-- Monitoring dialog: Description of an active VPN. [CHAR LIMIT=NONE]-->
     <string name="monitoring_description_named_vpn">You're connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
@@ -1102,6 +1148,12 @@
     <!-- Monitoring dialog: Link to open the VPN settings page [CHAR LIMIT=60] -->
     <string name="monitoring_description_vpn_settings">Open VPN Settings</string>
 
+    <!-- Monitoring dialog: Space that separates the CA certs body text and the "Open trusted credentials" link that follows it. [CHAR LIMIT=5] -->
+    <string name="monitoring_description_ca_cert_settings_separator">" "</string>
+
+    <!-- Monitoring dialog: Link to open the settings page containing CA certificates [CHAR LIMIT=NONE] -->
+    <string name="monitoring_description_ca_cert_settings">Open trusted credentials</string>
+
     <!-- Monitoring dialog: Network logging text [CHAR LIMIT=400] -->
     <string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information, contact your admin.</string>
 
@@ -1115,7 +1167,9 @@
     <string name="legacy_vpn_name">VPN</string>
 
     <!-- Monitoring dialog text for single app (no profile or device owner) [CHAR LIMIT=400] -->
-    <string name="monitoring_description_app">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your network activity including emails, apps, and websites.</string>
+    <string name="monitoring_description_app">You\'re connected to
+        <xliff:g id="application">%1$s</xliff:g>, which can monitor your network activity,
+        including emails, apps, and websites.</string>
 
     <!-- Monitoring dialog text for single app (inside personal profile) [CHAR LIMIT=400] -->
     <string name="monitoring_description_app_personal">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps, and websites.</string>
@@ -1124,10 +1178,18 @@
     <string name="branded_monitoring_description_app_personal">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps, and websites.</string>
 
     <!-- Monitoring dialog text for single app (inside work profile) [CHAR LIMIT=400] -->
-    <string name="monitoring_description_app_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nFor more information, contact your admin.</string>
+    <string name="monitoring_description_app_work">Your work profile is managed by
+        <xliff:g id="organization">%1$s</xliff:g>. The profile is connected to
+        <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity,
+        including emails, apps, and websites.\n\nFor more information, contact your admin.</string>
 
     <!-- Monitoring dialog text for multiple apps (in personal and work profiles) [CHAR LIMIT=400] -->
-    <string name="monitoring_description_app_personal_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application_work">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nYou\'re also connected to <xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network activity.</string>
+    <string name="monitoring_description_app_personal_work">Your work profile is managed by
+        <xliff:g id="organization">%1$s</xliff:g>. The profile is connected to
+        <xliff:g id="application_work">%2$s</xliff:g>, which can monitor your work network activity,
+        including emails, apps, and websites.\n\nYou\'re also connected to
+        <xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network
+        activity.</string>
 
     <!-- Indication on the keyguard that appears when the user disables trust agents until the next time they unlock manually. [CHAR LIMIT=NONE] -->
     <string name="keyguard_indication_trust_disabled">Device will stay locked until you manually unlock</string>
@@ -1816,6 +1878,15 @@
     <!-- PiP BTW notification description. [CHAR LIMIT=NONE] -->
     <string name="pip_notification_message">If you don’t want <xliff:g id="name" example="Google Maps">%s</xliff:g> to use this feature, tap to open settings and turn it off.</string>
 
+    <!-- PiP section of the tuner. [CHAR LIMIT=NONE] -->
+    <string name="picture_in_picture" translatable="false">Picture-in-Picture</string>
+
+    <!-- PiP minimize title. [CHAR LIMIT=NONE]-->
+    <string name="pip_minimize_title" translatable="false">Minimize</string>
+
+    <!-- PiP minimize description. [CHAR LIMIT=NONE] -->
+    <string name="pip_minimize_description" translatable="false">Drag or fling the PIP to the edges of the screen to minimize it.</string>
+
     <!-- Tuner string -->
     <string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
     <!-- Tuner string -->
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index bc3edd5..908fb20 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -122,6 +122,18 @@
     </PreferenceScreen>
 
     <PreferenceScreen
+      android:key="picture_in_picture"
+      android:title="@string/picture_in_picture">
+
+      <com.android.systemui.tuner.TunerSwitch
+        android:key="pip_minimize"
+        android:title="@string/pip_minimize_title"
+        android:summary="@string/pip_minimize_description"
+        sysui:defValue="false" />
+
+    </PreferenceScreen>
+
+    <PreferenceScreen
       android:key="doze"
       android:title="@string/tuner_doze">
 
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 374086d..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;
@@ -32,6 +33,7 @@
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.PluginManagerImpl;
+import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -79,6 +81,7 @@
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.leak.LeakReporter;
+import com.android.systemui.volume.VolumeDialogControllerImpl;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -252,6 +255,11 @@
         mProviders.put(LocalBluetoothManager.class, () ->
                 LocalBluetoothManager.getInstance(mContext, null));
 
+        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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index a95713f..5a04108d 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -32,9 +32,10 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.NotificationMenuRow;
 
 import java.util.HashMap;
 
@@ -267,7 +268,7 @@
                 mCurrView = mCallback.getChildAtPosition(ev);
 
                 if (mCurrView != null) {
-                    onDownUpdate(mCurrView);
+                    onDownUpdate(mCurrView, ev);
                     mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
                     mVelocityTracker.addMovement(ev);
                     mInitialTouchPos = getPos(ev);
@@ -285,8 +286,12 @@
                                         mCurrView.getLocationOnScreen(mTmpPos);
                                         final int x = (int) ev.getRawX() - mTmpPos[0];
                                         final int y = (int) ev.getRawY() - mTmpPos[1];
-                                        mLongPressListener.onLongPress(mCurrView, x, y,
-                                                NotificationMenuRow.getLongpressMenuItem(mContext));
+                                        MenuItem menuItem = null;
+                                        if (mCurrView instanceof ExpandableNotificationRow) {
+                                            menuItem = ((ExpandableNotificationRow) mCurrView)
+                                                    .getProvider().getLongpressMenuItem(mContext);
+                                        }
+                                        mLongPressListener.onLongPress(mCurrView, x, y, menuItem);
                                     }
                                 }
                             };
@@ -479,14 +484,14 @@
     /**
      * Called when there's a down event.
      */
-    public void onDownUpdate(View currView) {
+    public void onDownUpdate(View currView, MotionEvent ev) {
         // Do nothing
     }
 
     /**
      * Called on a move event.
      */
-    protected void onMoveUpdate(View view, float totalTranslation, float delta) {
+    protected void onMoveUpdate(View view, MotionEvent ev, float totalTranslation, float delta) {
         // Do nothing
     }
 
@@ -580,7 +585,7 @@
 
                     setTranslation(mCurrView, mTranslation + delta);
                     updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
-                    onMoveUpdate(mCurrView, mTranslation + delta, delta);
+                    onMoveUpdate(mCurrView, ev, mTranslation + delta, delta);
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -635,7 +640,7 @@
         return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView);
     }
 
-    protected boolean isDismissGesture(MotionEvent ev) {
+    public boolean isDismissGesture(MotionEvent ev) {
         boolean falsingDetected = mCallback.isAntiFalsingNeeded();
         if (mFalsingManager.isClassiferEnabled()) {
             falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
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/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index af7e9b4..5237244 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui;
 
-import android.content.ComponentName;
 import android.content.Context;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -39,7 +38,7 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.volume.VolumeDialogController;
+import com.android.systemui.volume.VolumeDialogControllerImpl;
 
 /**
  * Class factory to provide customizable SystemUI components.
@@ -89,11 +88,6 @@
         return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim);
     }
 
-    public VolumeDialogController createVolumeDialogController(Context context,
-            ComponentName name) {
-        return new VolumeDialogController(context, name);
-    }
-
     public NotificationIconAreaController createNotificationIconAreaController(Context context,
             StatusBar statusBar) {
         return new NotificationIconAreaController(context, statusBar);
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
index 67aa4dc..5a19e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -15,11 +15,12 @@
  */
 package com.android.systemui.car;
 
-import android.content.ComponentName;
 import android.content.Context;
+import android.util.ArrayMap;
 
+import com.android.systemui.Dependency.DependencyProvider;
 import com.android.systemui.SystemUIFactory;
-import com.android.systemui.volume.VolumeDialogController;
+import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.volume.car.CarVolumeDialogController;
 
 /**
@@ -27,8 +28,9 @@
  */
 public class CarSystemUIFactory extends SystemUIFactory {
     @Override
-    public VolumeDialogController createVolumeDialogController(Context context,
-            ComponentName name) {
-        return new CarVolumeDialogController(context, name);
+    public void injectDependencies(ArrayMap<Object, DependencyProvider> providers,
+            Context context) {
+        super.injectDependencies(providers, context);
+        providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 57c75bf..2b6ea15 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -22,6 +22,7 @@
 import android.app.FragmentManager.FragmentLifecycleCallbacks;
 import android.app.FragmentManagerNonConfig;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
@@ -33,9 +34,7 @@
 
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.Dependency;
-import com.android.systemui.SystemUIApplication;
 import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -48,7 +47,8 @@
     private final Context mContext;
     private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>();
     private final View mRootView;
-    private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges();
+    private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
+            ActivityInfo.CONFIG_FONT_SCALE);
     private final FragmentService mManager;
     private final PluginFragmentManager mPlugins = new PluginFragmentManager();
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index f70d5b4..a0f491f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -37,8 +37,10 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.tuner.TunerService;
 
 import java.io.PrintWriter;
 
@@ -46,9 +48,11 @@
  * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
  * the PIP.
  */
-public class PipTouchHandler {
+public class PipTouchHandler implements TunerService.Tunable {
     private static final String TAG = "PipTouchHandler";
 
+    private static final String TUNER_KEY_MINIMIZE = "pip_minimize";
+
     // These values are used for metrics and should never change
     private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
     private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1;
@@ -97,6 +101,9 @@
                 }
             };
 
+    // Allow the PIP to be dragged to the edge of the screen to be minimized.
+    private boolean mEnableMinimize = false;
+
     // Behaviour states
     private boolean mIsMenuVisible;
     private boolean mIsMinimized;
@@ -104,6 +111,8 @@
     private int mImeHeight;
     private float mSavedSnapFraction = -1f;
     private boolean mSendingHoverAccessibilityEvents;
+    private boolean mMovementWithinMinimize;
+    private boolean mMovementWithinDismiss;
 
     // Touch state
     private final PipTouchState mTouchState;
@@ -167,6 +176,9 @@
         mExpandedShortestEdgeSize = context.getResources().getDimensionPixelSize(
                 R.dimen.pip_expanded_shortest_edge_size);
 
+        // Register any tuner settings changes
+        Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_MINIMIZE);
+
         // Register the listener for input consumer touch events
         inputConsumerController.setTouchListener(this::handleTouchEvent);
         inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
@@ -187,6 +199,20 @@
         }
     }
 
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (newValue == null) {
+            // Reset back to default
+            mEnableMinimize = false;
+            return;
+        }
+        switch (key) {
+            case TUNER_KEY_MINIMIZE:
+                mEnableMinimize = Integer.parseInt(newValue) != 0;
+                break;
+        }
+    }
+
     public void onConfigurationChanged() {
         mMotionHelper.onConfigurationChanged();
         mMotionHelper.synchronizePinnedStackBounds();
@@ -366,6 +392,9 @@
      * Sets the minimized state.
      */
     void setMinimizedStateInternal(boolean isMinimized) {
+        if (!mEnableMinimize) {
+            return;
+        }
         setMinimizedState(isMinimized, false /* fromController */);
     }
 
@@ -373,6 +402,9 @@
      * Sets the minimized state.
      */
     void setMinimizedState(boolean isMinimized, boolean fromController) {
+        if (!mEnableMinimize) {
+            return;
+        }
         if (mIsMinimized != isMinimized) {
             MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED,
                     isMinimized);
@@ -435,6 +467,8 @@
      * Gesture controlling normal movement of the PIP.
      */
     private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
+        // Whether the PiP was on the left side of the screen at the start of the gesture
+        private boolean mStartedOnLeft;
 
         @Override
         public void onDown(PipTouchState touchState) {
@@ -442,6 +476,10 @@
                 return;
             }
 
+            mStartedOnLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
+            mMovementWithinMinimize = true;
+            mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
+
             // If the menu is still visible, and we aren't minimized, then just poke the menu
             // so that it will timeout after the user stops touching it
             if (mMenuController.isMenuVisible() && !mIsMinimized) {
@@ -475,7 +513,7 @@
                 final PointF lastDelta = touchState.getLastTouchDelta();
                 float left = mTmpBounds.left + lastDelta.x;
                 float top = mTmpBounds.top + lastDelta.y;
-                if (!touchState.allowDraggingOffscreen()) {
+                if (!touchState.allowDraggingOffscreen() || !mEnableMinimize) {
                     left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left));
                 }
                 if (ENABLE_DISMISS_DRAG_TO_EDGE) {
@@ -493,6 +531,18 @@
                 if (ENABLE_DISMISS_DRAG_TO_EDGE) {
                     updateDismissFraction();
                 }
+
+                final PointF curPos = touchState.getLastTouchPosition();
+                if (mMovementWithinMinimize) {
+                    // Track if movement remains near starting edge to identify swipes to minimize
+                    mMovementWithinMinimize = mStartedOnLeft
+                            ? curPos.x <= mMovementBounds.left + mTmpBounds.width()
+                            : curPos.x >= mMovementBounds.right;
+                }
+                if (mMovementWithinDismiss) {
+                    // Track if movement remains near the bottom edge to identify swipe to dismiss
+                    mMovementWithinDismiss = curPos.y >= mMovementBounds.bottom;
+                }
                 return true;
             }
             return false;
@@ -526,8 +576,15 @@
             }
 
             if (touchState.isDragging()) {
-                final boolean onLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
-                boolean isFlingToBot = isFlingTowardsEdge(touchState, 4 /* bottom */);
+                final PointF vel = touchState.getVelocity();
+                final float velocity = PointF.length(vel.x, vel.y);
+                final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
+                final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
+                final boolean isFlingToBot = isFling
+                        && !isHorizontal && mMovementWithinDismiss && vel.y > 0;
+                final boolean isFlingToEdge = isFling && isHorizontal && mMovementWithinMinimize
+                        && (mStartedOnLeft ? vel.x < 0 : vel.x > 0);
+
                 if (ENABLE_DISMISS_DRAG_TO_EDGE
                         && (mMotionHelper.shouldDismissPip() || isFlingToBot)) {
                     mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds(),
@@ -536,8 +593,8 @@
                             MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
                             METRIC_VALUE_DISMISSED_BY_DRAG);
                     return true;
-                } else if (!mIsMinimized && (mMotionHelper.shouldMinimizePip()
-                        || isFlingTowardsEdge(touchState, onLeft ? 2 : 3))) {
+                } else if (mEnableMinimize &&
+                        !mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) {
                     // Pip should be minimized
                     setMinimizedStateInternal(true);
                     if (mMenuController.isMenuVisible()) {
@@ -563,9 +620,7 @@
                     mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds);
                 }
 
-                final PointF vel = mTouchState.getVelocity();
-                final float velocity = PointF.length(vel.x, vel.y);
-                if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+                if (isFling) {
                     mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
                             mUpdateScrimListener);
                 } else {
@@ -585,42 +640,6 @@
     };
 
     /**
-     * @return whether the gesture ending in {@param vel} is fast enough to be a fling and towards
-     *         the provided {@param edge} where:
-     *
-     *         1 = top
-     *         2 = left
-     *         3 = right
-     *         4 = bottom
-     */
-    private boolean isFlingTowardsEdge(PipTouchState touchState, int edge) {
-        final PointF vel = touchState.getVelocity();
-        final PointF downPos = touchState.getDownTouchPosition();
-        final Rect bounds = mMotionHelper.getBounds();
-        final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
-        final boolean isFling =
-                PointF.length(vel.x, vel.y) > mFlingAnimationUtils.getMinVelocityPxPerSecond();
-        if (!isFling) {
-            return false;
-        }
-        switch (edge) {
-            case 1: // top
-                return !isHorizontal && vel.y < 0
-                        && downPos.y <= mMovementBounds.top + bounds.height();
-            case 2: // left
-                return isHorizontal && vel.x < 0
-                        && downPos.x <= mMovementBounds.left + bounds.width();
-            case 3: // right
-                return isHorizontal && vel.x > 0
-                        && downPos.x >= mMovementBounds.right;
-            case 4: // bottom
-                return !isHorizontal && vel.y > 0
-                        && downPos.y >= mMovementBounds.bottom;
-        }
-        return false;
-    }
-
-    /**
      * Updates the current movement bounds based on whether the menu is currently visible.
      */
     private void updateMovementBounds(boolean isExpanded) {
@@ -643,6 +662,7 @@
         pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
         pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
         pw.println(innerPrefix + "mEnableDragToDismiss=" + ENABLE_DISMISS_DRAG_TO_TARGET);
+        pw.println(innerPrefix + "mEnableMinimize=" + mEnableMinimize);
         mSnapAlgorithm.dump(pw, innerPrefix);
         mTouchState.dump(pw, innerPrefix);
         mMotionHelper.dump(pw, innerPrefix);
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/ButtonRelativeLayout.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ButtonRelativeLayout.java
new file mode 100644
index 0000000..2c17b87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ButtonRelativeLayout.java
@@ -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 com.android.systemui.qs.tileimpl;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+import android.widget.RelativeLayout;
+
+public class ButtonRelativeLayout extends RelativeLayout {
+
+    public ButtonRelativeLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return Button.class.getName();
+    }
+}
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/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index d2ae6e9f..5c99809 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -103,6 +103,8 @@
         }
         mDivider.setVisibility(state.dualTarget ? View.VISIBLE : View.INVISIBLE);
         mExpandIndicator.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE);
+        mLabelContainer.setContentDescription(state.dualTarget ? state.dualLabelContentDescription
+                : null);
         if (state.dualTarget != mLabelContainer.isClickable()) {
             mLabelContainer.setClickable(state.dualTarget);
             mLabelContainer.setLongClickable(state.dualTarget);
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 d552a2e..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);
     }
 
@@ -148,13 +147,7 @@
             state.state = Tile.STATE_INACTIVE;
         }
 
-        CharSequence bluetoothName = state.label;
-        if (connected) {
-            bluetoothName = state.dualLabelContentDescription = mContext.getString(
-                    R.string.accessibility_bluetooth_name, state.label);
-        }
-        state.dualLabelContentDescription = bluetoothName;
-        state.contentDescription = state.contentDescription + "," + mContext.getString(
+        state.dualLabelContentDescription = mContext.getResources().getString(
                 R.string.accessibility_quick_settings_open_settings, getTileLabel());
         state.expandedAccessibilityClassName = Switch.class.getName();
     }
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 d3586c4..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);
@@ -198,6 +197,8 @@
         if (valueChanged) {
             fireToggleStateChanged(state.value);
         }
+        state.dualLabelContentDescription = mContext.getResources().getString(
+                R.string.accessibility_quick_settings_open_settings, getTileLabel());
         state.expandedAccessibilityClassName = Switch.class.getName();
     }
 
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 0270641..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);
     }
 
@@ -157,7 +156,6 @@
         state.activityIn = cb.enabled && cb.activityIn;
         state.activityOut = cb.enabled && cb.activityOut;
         final StringBuffer minimalContentDescription = new StringBuffer();
-        final StringBuffer expandedContentDescription = new StringBuffer();
         final Resources r = mContext.getResources();
         if (!state.value) {
             state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_disabled);
@@ -175,26 +173,14 @@
         minimalContentDescription.append(
                 mContext.getString(R.string.quick_settings_wifi_label)).append(",");
         if (state.value) {
-            expandedContentDescription.append(
-                    r.getString(R.string.quick_settings_wifi_on_label)).append(",");
             if (wifiConnected) {
                 minimalContentDescription.append(cb.wifiSignalContentDescription).append(",");
                 minimalContentDescription.append(removeDoubleQuotes(cb.enabledDesc));
-                expandedContentDescription.append(cb.wifiSignalContentDescription).append(",");
-                expandedContentDescription.append(removeDoubleQuotes(cb.enabledDesc));
             }
-        } else {
-            expandedContentDescription.append(
-                    r.getString(R.string.quick_settings_wifi_off_label));
         }
-        expandedContentDescription.append(",").append(
-                r.getString(R.string.accessibility_quick_settings_open_settings, getTileLabel()));
-        state.contentDescription = expandedContentDescription;
-        CharSequence wifiName = state.label;
-        if (cb.connected) {
-            wifiName = r.getString(R.string.accessibility_wifi_name, state.label);
-        }
-        state.dualLabelContentDescription = wifiName;
+        state.contentDescription = minimalContentDescription;
+        state.dualLabelContentDescription = r.getString(
+                R.string.accessibility_quick_settings_open_settings, getTileLabel());
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
     }
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/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 00968ee..9176f57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -43,6 +43,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Chronometer;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
 
@@ -50,10 +51,15 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.NotificationColorUtil;
 import com.android.internal.widget.CachingIconView;
+import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.statusbar.NotificationGuts.GutsContent;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.NotificationInflater;
@@ -71,10 +77,13 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public class ExpandableNotificationRow extends ActivatableNotificationView {
+public class ExpandableNotificationRow extends ActivatableNotificationView
+        implements PluginListener<NotificationMenuRowPlugin> {
 
     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
+    private static final int MENU_VIEW_INDEX = 0;
+
     private final NotificationInflater mNotificationInflater;
     private int mIconTransformContentShift;
     private int mIconTransformContentShiftNoIcon;
@@ -129,7 +138,6 @@
     private int mNotificationColor;
     private ExpansionLogger mLogger;
     private String mLoggingKey;
-    private NotificationMenuRow mMenuRow;
     private NotificationGuts mGuts;
     private NotificationData.Entry mEntry;
     private StatusBarNotification mStatusBarNotification;
@@ -141,7 +149,7 @@
     private boolean mChildrenExpanded;
     private boolean mIsSummaryWithChildren;
     private NotificationChildrenContainer mChildrenContainer;
-    private ViewStub mMenuRowStub;
+    private NotificationMenuRowPlugin mMenuRow;
     private ViewStub mGutsStub;
     private boolean mIsSystemChildExpanded;
     private boolean mIsPinned;
@@ -422,7 +430,7 @@
 
     public void setAppName(String appName) {
         mAppName = appName;
-        if (mMenuRow != null) {
+        if (mMenuRow != null && mMenuRow.getMenuView() != null) {
             mMenuRow.setAppName(mAppName);
         }
     }
@@ -496,7 +504,7 @@
 
     @Override
     protected boolean handleSlideBack() {
-        if (mMenuRow != null && mMenuRow.isVisible()) {
+        if (mMenuRow != null && mMenuRow.isMenuVisible()) {
             animateTranslateNotification(0 /* targetLeft */);
             return true;
         }
@@ -725,12 +733,65 @@
     }
 
     public void setGutsView(MenuItem item) {
-        if (mGuts != null) {
-            item.gutsContent.setInteractionListener(mGuts);
-            mGuts.setGutsContent(item.gutsContent);
+        if (mGuts != null && item.getGutsView() instanceof GutsContent) {
+            ((GutsContent) item.getGutsView()).setGutsParent(mGuts);
+            mGuts.setGutsContent((GutsContent) item.getGutsView());
         }
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        Dependency.get(PluginManager.class).addPluginListener(this,
+                NotificationMenuRowPlugin.class, false /* Allow multiple */);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        Dependency.get(PluginManager.class).removePluginListener(this);
+    }
+
+    @Override
+    public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) {
+        boolean existed = mMenuRow.getMenuView() != null;
+        if (existed) {
+            removeView(mMenuRow.getMenuView());
+        }
+        mMenuRow = plugin;
+        if (mMenuRow.useDefaultMenuItems()) {
+            mMenuRow.setMenuItems(NotificationMenuRow.getDefaultMenuItems(mContext));
+        }
+        if (existed) {
+            createMenu();
+        }
+    }
+
+    @Override
+    public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
+        boolean existed = mMenuRow.getMenuView() != null;
+        mMenuRow = new NotificationMenuRow(mContext); // Back to default
+        if (existed) {
+            createMenu();
+        }
+    }
+
+    public NotificationMenuRowPlugin createMenu() {
+        if (mMenuRow.getMenuView() == null) {
+            mMenuRow.createMenu(this);
+            mMenuRow.setAppName(mAppName);
+            FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
+                    LayoutParams.MATCH_PARENT);
+            addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp);
+        }
+        return mMenuRow;
+    }
+
+
+    public NotificationMenuRowPlugin getProvider() {
+        return mMenuRow;
+    }
+
     public void onDensityOrFontScaleChanged() {
         initDimens();
         if (mIsSummaryWithChildren) {
@@ -747,17 +808,13 @@
             mGuts.setVisibility(oldGuts.getVisibility());
             addView(mGuts, index);
         }
-        if (mMenuRow != null) {
-            View oldMenu = mMenuRow;
+        View oldMenu = mMenuRow.getMenuView();
+        if (oldMenu != null) {
             int menuIndex = indexOfChild(oldMenu);
             removeView(oldMenu);
-            mMenuRow = (NotificationMenuRow) LayoutInflater.from(mContext).inflate(
-                    R.layout.notification_menu_row, this, false);
-            mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
+            mMenuRow.createMenu(ExpandableNotificationRow.this);
             mMenuRow.setAppName(mAppName);
-            mMenuRow.setVisibility(oldMenu.getVisibility());
-            addView(mMenuRow, menuIndex);
-
+            addView(mMenuRow.getMenuView(), menuIndex);
         }
         for (NotificationContentView l : mLayouts) {
             l.reInflateViews();
@@ -1061,6 +1118,7 @@
         super(context, attrs);
         mFalsingManager = FalsingManager.getInstance(context);
         mNotificationInflater = new NotificationInflater(this);
+        mMenuRow = new NotificationMenuRow(mContext);
         initDimens();
     }
 
@@ -1113,15 +1171,6 @@
             l.setExpandClickListener(mExpandClickListener);
             l.setContainingNotification(this);
         }
-        mMenuRowStub = (ViewStub) findViewById(R.id.menu_row_stub);
-        mMenuRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
-            @Override
-            public void onInflate(ViewStub stub, View inflated) {
-                mMenuRow = (NotificationMenuRow) inflated;
-                mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
-                mMenuRow.setAppName(mAppName);
-            }
-        });
         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
             @Override
@@ -1145,13 +1194,12 @@
             }
         });
 
-        // Add the views that we translate to reveal the gear
+        // Add the views that we translate to reveal the menu
         mTranslateableViews = new ArrayList<View>();
         for (int i = 0; i < getChildCount(); i++) {
             mTranslateableViews.add(getChildAt(i));
         }
         // Remove views that don't translate
-        mTranslateableViews.remove(mMenuRowStub);
         mTranslateableViews.remove(mChildrenContainerStub);
         mTranslateableViews.remove(mGutsStub);
     }
@@ -1166,9 +1214,7 @@
             }
         }
         invalidateOutline();
-        if (mMenuRow != null) {
-            mMenuRow.resetState(true /* notify */);
-        }
+        mMenuRow.resetMenu();
     }
 
     public void animateTranslateNotification(final float leftTarget) {
@@ -1194,8 +1240,8 @@
             }
         }
         invalidateOutline();
-        if (mMenuRow != null) {
-            mMenuRow.updateMenuAlpha(translationX, getMeasuredWidth());
+        if (mMenuRow.getMenuView() != null) {
+            mMenuRow.onTranslationUpdate(translationX);
         }
     }
 
@@ -1232,8 +1278,8 @@
 
             @Override
             public void onAnimationEnd(Animator anim) {
-                if (!cancelled && mMenuRow != null && leftTarget == 0) {
-                    mMenuRow.resetState(true /* notify */);
+                if (!cancelled && leftTarget == 0) {
+                    mMenuRow.resetMenu();
                     mTranslateAnim = null;
                 }
             }
@@ -1242,20 +1288,6 @@
         return translateAnim;
     }
 
-    public float getSpaceForGear() {
-        if (mMenuRow != null) {
-            return mMenuRow.getSpaceForMenu();
-        }
-        return 0;
-    }
-
-    public NotificationMenuRow getSettingsRow() {
-        if (mMenuRow == null) {
-            mMenuRowStub.inflate();
-        }
-        return mMenuRow;
-    }
-
     public void inflateGuts() {
         if (mGuts == null) {
             mGutsStub.inflate();
@@ -1531,8 +1563,8 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         updateMaxHeights();
-        if (mMenuRow != null) {
-            mMenuRow.updateVerticalLocation();
+        if (mMenuRow.getMenuView() != null) {
+            mMenuRow.onHeightUpdate();
         }
         updateContentShiftHeight();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index fd1317e..2713f58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -51,8 +51,8 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.util.Set;
@@ -60,8 +60,7 @@
 /**
  * The guts of a notification revealed when performing a long press.
  */
-public class NotificationGuts extends FrameLayout
-        implements NotificationMenuRowProvider.GutsInteractionListener {
+public class NotificationGuts extends FrameLayout {
     private static final String TAG = "NotificationGuts";
     private static final long CLOSE_GUTS_DELAY = 8000;
 
@@ -78,10 +77,35 @@
 
     private GutsContent mGutsContent;
 
+    public interface GutsContent {
+
+        public void setGutsParent(NotificationGuts listener);
+
+        /**
+         * @return the view to be shown in the notification guts.
+         */
+        public View getContentView();
+
+        /**
+         * Called when the guts view have been told to close, typically after an outside
+         * interaction. Returning {@code true} here will prevent the guts view to close.
+         */
+        public boolean handleCloseControls(boolean save);
+
+        /**
+         * @return whether the notification associated with these guts is set to be removed.
+         */
+        public boolean willBeRemoved();
+    }
+
     public interface OnGutsClosedListener {
         public void onGutsClosed(NotificationGuts guts);
     }
 
+    interface OnSettingsClickListener {
+        void onClick(View v, int appUid);
+    }
+
     public NotificationGuts(Context context, AttributeSet attrs) {
         super(context, attrs);
         setWillNotDraw(false);
@@ -163,10 +187,6 @@
         }
     }
 
-    interface OnSettingsClickListener {
-        void onClick(View v, int appUid);
-    }
-
     public void closeControls(int x, int y, boolean save) {
         if (getWindowToken() == null) {
             if (mListener != null) {
@@ -251,14 +271,4 @@
     public boolean isExposed() {
         return mExposed;
     }
-
-    @Override
-    public void onInteraction(View view) {
-        resetFalsingCheck();
-    }
-
-    @Override
-    public void closeGuts(View view) {
-        closeControls(-1 /* x */, -1 /* y */, true /* notify */);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 54921a7..0398f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -53,8 +53,6 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
 import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
@@ -65,7 +63,7 @@
 /**
  * The guts of a notification revealed when performing a long press.
  */
-public class NotificationInfo extends LinearLayout implements GutsContent {
+public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
     private static final String TAG = "InfoGuts";
 
     private INotificationManager mINotificationManager;
@@ -79,7 +77,7 @@
     private View mChannelDisabledView;
     private Switch mChannelEnabledSwitch;
 
-    private GutsInteractionListener mGutsInteractionListener;
+    private NotificationGuts mGutsContainer;
 
     public NotificationInfo(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -282,8 +280,8 @@
 
         // Callback when checked.
         mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            if (mGutsInteractionListener != null) {
-                mGutsInteractionListener.onInteraction(NotificationInfo.this);
+            if (mGutsContainer != null) {
+                mGutsContainer.resetFalsingCheck();
             }
             updateSecondaryText();
         });
@@ -302,8 +300,8 @@
     }
 
     @Override
-    public void setInteractionListener(GutsInteractionListener listener) {
-        mGutsInteractionListener = listener;
+    public void setGutsParent(NotificationGuts guts) {
+        mGutsContainer = guts;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 534a719..5055dda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -16,276 +16,449 @@
 
 package com.android.systemui.statusbar;
 
+import java.util.ArrayList;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
+import android.os.Handler;
 import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
 
-import java.util.ArrayList;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.OnMenuClickListener;
-
-public class NotificationMenuRow extends FrameLayout
-        implements PluginListener<NotificationMenuRowProvider>, View.OnClickListener {
+public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener {
 
     private static final int ICON_ALPHA_ANIM_DURATION = 200;
+    private static final long SHOW_MENU_DELAY = 60;
+    private static final long SWIPE_MENU_TIMING = 200;
+
+    private static final int NOTIFICATION_INFO_INDEX = 1;
 
     private ExpandableNotificationRow mParent;
-    private OnMenuClickListener mListener;
-    private NotificationMenuRowProvider mMenuProvider;
-    private ArrayList<MenuItem> mMenuItems = new ArrayList<>();
+
+    private Context mContext;
+    private FrameLayout mMenuContainer;
+    private ArrayList<MenuItem> mMenuItems;
+    private OnMenuEventListener mMenuListener;
 
     private ValueAnimator mFadeAnimator;
-    private boolean mMenuFadedIn = false;
-    private boolean mAnimating = false;
-    private boolean mOnLeft = true;
-    private boolean mDismissing = false;
-    private boolean mSnapping = false;
-    private boolean mIconsPlaced = false;
+    private boolean mAnimating;
+    private boolean mMenuFadedIn;
+
+    private boolean mOnLeft;
+    private boolean mIconsPlaced;
+
+    private boolean mDismissing;
+    private boolean mSnapping;
+    private float mTranslation;
 
     private int[] mIconLocation = new int[2];
     private int[] mParentLocation = new int[2];
 
     private float mHorizSpaceForIcon;
     private int mVertSpaceForIcons;
-
     private int mIconPadding;
-    private int mIconTint;
 
     private float mAlpha = 0f;
 
+    private CheckForDrag mCheckForDrag;
+    private Handler mHandler;
+
+    private boolean mMenuSnappedTo;
+    private boolean mMenuSnappedOnLeft;
+    private boolean mShouldShowMenu;
+
+    private NotificationSwipeActionHelper mSwipeHelper;
+
     public NotificationMenuRow(Context context) {
-        this(context, null);
-    }
-
-    public NotificationMenuRow(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs);
-        mMenuItems.addAll(getDefaultNotificationMenuItems());
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        Dependency.get(PluginManager.class).addPluginListener(
-                this, NotificationMenuRowProvider.class, false /* Allow multiple */);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(PluginManager.class).removePluginListener(this);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        final Resources res = getResources();
+        mContext = context;
+        final Resources res = context.getResources();
+        mShouldShowMenu = res.getBoolean(R.bool.config_showNotificationGear);
         mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
         mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
         mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
-        mIconTint = res.getColor(R.color.notification_gear_color);
-        updateMenu(false /* notify */);
+        mHandler = new Handler();
+        mMenuItems = getDefaultMenuItems(context);
     }
 
-    public static MenuItem getLongpressMenuItem(Context context) {
-        Resources res = context.getResources();
-        Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
-        String settingsDescription = res.getString(R.string.notification_menu_gear_description);
-        NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate(
-                R.layout.notification_info, null, false);
-        MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
-        return settings;
+    @Override
+    public ArrayList<MenuItem> getMenuItems(Context context) {
+        return mMenuItems;
     }
 
-    public ArrayList<MenuItem> getDefaultNotificationMenuItems() {
-        ArrayList<MenuItem> items = new ArrayList<MenuItem>();
-        Resources res = getResources();
-
-        Drawable snoozeIcon = res.getDrawable(R.drawable.ic_snooze);
-        NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(mContext)
-                .inflate(R.layout.notification_snooze, null, false);
-        String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
-        MenuItem snooze = new MenuItem(snoozeIcon, snoozeDescription, content);
-        items.add(snooze);
-
-        Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
-        String settingsDescription = res.getString(R.string.notification_menu_gear_description);
-        NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(mContext).inflate(
-                R.layout.notification_info, null, false);
-        MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
-        items.add(settings);
-        return items;
+    @Override
+    public MenuItem getLongpressMenuItem(Context context) {
+        return mMenuItems.get(NOTIFICATION_INFO_INDEX);
     }
 
-    private void updateMenu(boolean notify) {
-        removeAllViews();
-        mMenuItems.clear();
-        if (mMenuProvider != null) {
-            mMenuItems.addAll(mMenuProvider.getMenuItems(getContext()));
-        }
-        mMenuItems.addAll(getDefaultNotificationMenuItems());
+    @Override
+    public void setSwipeActionHelper(NotificationSwipeActionHelper helper) {
+        mSwipeHelper = helper;
+    }
+
+    @Override
+    public void setMenuClickListener(OnMenuEventListener listener) {
+        mMenuListener = listener;
+    }
+
+    @Override
+    public void createMenu(ViewGroup parent) {
+        mParent = (ExpandableNotificationRow) parent;
+        mMenuContainer = new FrameLayout(mContext);
         for (int i = 0; i < mMenuItems.size(); i++) {
-            final View v = createMenuView(mMenuItems.get(i));
-            mMenuItems.get(i).menuView = v;
+            addMenuView(mMenuItems.get(i), mMenuContainer);
         }
-        resetState(notify);
+        resetState(false);
     }
 
-    private View createMenuView(MenuItem item) {
-        AlphaOptimizedImageView iv = new AlphaOptimizedImageView(getContext());
-        addView(iv);
-        iv.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
-        iv.setImageDrawable(item.icon);
-        iv.setOnClickListener(this);
-        iv.setColorFilter(mIconTint);
-        iv.setAlpha(mAlpha);
-        FrameLayout.LayoutParams lp = (LayoutParams) iv.getLayoutParams();
-        lp.width = (int) mHorizSpaceForIcon;
-        lp.height = (int) mHorizSpaceForIcon;
-        return iv;
+    @Override
+    public boolean isMenuVisible() {
+        return mAlpha > 0;
     }
 
-    public void resetState(boolean notify) {
+    @Override
+    public View getMenuView() {
+        return mMenuContainer;
+    }
+
+    @Override
+    public void resetMenu() {
+        resetState(true);
+    }
+
+    private void resetState(boolean notify) {
         setMenuAlpha(0f);
         mIconsPlaced = false;
         mMenuFadedIn = false;
         mAnimating = false;
         mSnapping = false;
         mDismissing = false;
-        setMenuLocation(mOnLeft ? 1 : -1 /* on left */);
-        if (mListener != null && notify) {
-            mListener.onMenuReset(mParent);
+        mMenuSnappedTo = false;
+        setMenuLocation();
+        if (mMenuListener != null && notify) {
+            mMenuListener.onMenuReset(mParent);
         }
     }
 
-    public void setMenuClickListener(OnMenuClickListener listener) {
-        mListener = listener;
+    @Override
+    public boolean onTouchEvent(View view, MotionEvent ev, float velocity) {
+        final int action = ev.getActionMasked();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mSnapping = false;
+                if (mFadeAnimator != null) {
+                    mFadeAnimator.cancel();
+                }
+                mHandler.removeCallbacks(mCheckForDrag);
+                mCheckForDrag = null;
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                mSnapping = false;
+                // If the menu is visible and the movement is towards it it's not a location change.
+                boolean locationChange = isTowardsMenu(mTranslation)
+                        ? false : isMenuLocationChange();
+                if (locationChange) {
+                    // Don't consider it "snapped" if location has changed.
+                    mMenuSnappedTo = false;
+
+                    // Changed directions, make sure we check to fade in icon again.
+                    if (!mHandler.hasCallbacks(mCheckForDrag)) {
+                        // No check scheduled, set null to schedule a new one.
+                        mCheckForDrag = null;
+                    } else {
+                        // Check scheduled, reset alpha and update location; check will fade it in
+                        setMenuAlpha(0f);
+                        setMenuLocation();
+                    }
+                }
+                if (mShouldShowMenu
+                        && !NotificationStackScrollLayout.isPinnedHeadsUp(view)
+                        && !mParent.areGutsExposed()
+                        && (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag))) {
+                    // Only show the menu if we're not a heads up view and guts aren't exposed.
+                    mCheckForDrag = new CheckForDrag();
+                    mHandler.postDelayed(mCheckForDrag, SHOW_MENU_DELAY);
+                }
+                break;
+
+            case MotionEvent.ACTION_UP:
+                return handleUpEvent(ev, view, velocity);
+        }
+        return false;
     }
 
-    public void setNotificationRowParent(ExpandableNotificationRow parent) {
-        mParent = parent;
-        setMenuLocation(mOnLeft ? 1 : -1);
+    private boolean handleUpEvent(MotionEvent ev, View animView, float velocity) {
+        // If the menu should not be shown, then there is no need to check if the a swipe
+        // should result in a snapping to the menu. As a result, just check if the swipe
+        // was enough to dismiss the notification.
+        if (!mShouldShowMenu) {
+            if (mSwipeHelper.isDismissGesture(ev)) {
+                dismiss(animView, velocity);
+            } else {
+                snapBack(animView, velocity);
+            }
+            return true;
+        }
+
+        final boolean gestureTowardsMenu = isTowardsMenu(velocity);
+        final boolean gestureFastEnough =
+                mSwipeHelper.getMinDismissVelocity() <= Math.abs(velocity);
+        final boolean gestureFarEnough =
+                mSwipeHelper.swipedFarEnough(mTranslation, mParent.getWidth());
+        final double timeForGesture = ev.getEventTime() - ev.getDownTime();
+        final boolean showMenuForSlowOnGoing = !mParent.canViewBeDismissed()
+                && timeForGesture >= SWIPE_MENU_TIMING;
+
+        final float targetLeft = mOnLeft ? getSpaceForMenu() : -getSpaceForMenu();
+        if (mMenuSnappedTo && isMenuVisible()) {
+            if (mMenuSnappedOnLeft == mOnLeft) {
+                boolean coveringMenu = Math.abs(mTranslation) <= getSpaceForMenu() * 0.6f;
+                if (gestureTowardsMenu || coveringMenu) {
+                    // Gesture is towards or covering the menu or a dismiss
+                    snapBack(animView, 0);
+                } else if (mSwipeHelper.isDismissGesture(ev)) {
+                    dismiss(animView, velocity);
+                } else {
+                    // Didn't move enough to dismiss or cover, snap to the menu
+                    showMenu(animView, targetLeft, velocity);
+                }
+            } else if ((!gestureFastEnough && swipedEnoughToShowMenu())
+                    || (gestureTowardsMenu && !gestureFarEnough)) {
+                // The menu has been snapped to previously, however, the menu is now on the
+                // other side. If gesture is towards menu and not too far snap to the menu.
+                showMenu(animView, targetLeft, velocity);
+            } else if (mSwipeHelper.isDismissGesture(ev)) {
+                dismiss(animView, velocity);
+            } else {
+                snapBack(animView, velocity);
+            }
+        } else if (((!gestureFastEnough || showMenuForSlowOnGoing)
+                && swipedEnoughToShowMenu())
+                || gestureTowardsMenu) {
+            // Menu has not been snapped to previously and this is menu revealing gesture
+            showMenu(animView, targetLeft, velocity);
+        } else if (mSwipeHelper.isDismissGesture(ev)) {
+            dismiss(animView, velocity);
+        } else {
+            snapBack(animView, velocity);
+        }
+        return true;
     }
 
+    private void showMenu(View animView, float targetLeft, float velocity) {
+        mMenuSnappedTo = true;
+        mMenuSnappedOnLeft = mOnLeft;
+        mMenuListener.onMenuShown(animView);
+        mSwipeHelper.snap(animView, targetLeft, velocity);
+    }
+
+    private void snapBack(View animView, float velocity) {
+        mMenuSnappedTo = false;
+        mSnapping = true;
+        mSwipeHelper.snap(animView, 0 /* leftTarget */, velocity);
+    }
+
+    private void dismiss(View animView, float velocity) {
+        mMenuSnappedTo = false;
+        mDismissing = true;
+        mSwipeHelper.dismiss(animView, velocity);
+    }
+
+    private boolean swipedEnoughToShowMenu() {
+        // If the notification can't be dismissed then how far it can move is
+        // restricted -- reduce the distance it needs to move in this case.
+        final float multiplier = mParent.canViewBeDismissed() ? 0.4f : 0.2f;
+        final float snapBackThreshold = getSpaceForMenu() * multiplier;
+        return !mSwipeHelper.swipedFarEnough(0, 0) && isMenuVisible() && (mOnLeft
+                ? mTranslation > snapBackThreshold
+                : mTranslation < -snapBackThreshold);
+    }
+
+    /**
+     * Returns whether the gesture is towards the menu location or not.
+     */
+    private boolean isTowardsMenu(float movement) {
+        return isMenuVisible()
+                && ((mOnLeft && movement <= 0)
+                        || (!mOnLeft && movement >= 0));
+    }
+
+    @Override
     public void setAppName(String appName) {
-        Resources res = getResources();
+        if (appName == null) {
+            return;
+        }
+        Resources res = mContext.getResources();
         final int count = mMenuItems.size();
         for (int i = 0; i < count; i++) {
             MenuItem item = mMenuItems.get(i);
             String description = String.format(
                     res.getString(R.string.notification_menu_accessibility),
-                    appName, item.menuDescription);
-            item.menuView.setContentDescription(description);
+                    appName, item.getContentDescription());
+            View menuView = item.getMenuView();
+            if (menuView != null) {
+                menuView.setContentDescription(description);
+            }
         }
     }
 
-    public ExpandableNotificationRow getNotificationParent() {
-        return mParent;
-    }
-
-    public void setMenuAlpha(float alpha) {
-        mAlpha = alpha;
-        if (alpha == 0) {
-            mMenuFadedIn = false; // Can fade in again once it's gone.
-            setVisibility(View.INVISIBLE);
+    @Override
+    public void onHeightUpdate() {
+        if (mParent == null || mMenuItems.size() == 0) {
+            return;
+        }
+        int parentHeight = mParent.getCollapsedHeight();
+        float translationY;
+        if (parentHeight < mVertSpaceForIcons) {
+            translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
         } else {
-            setVisibility(View.VISIBLE);
+            translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
         }
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            getChildAt(i).setAlpha(mAlpha);
-        }
+        mMenuContainer.setTranslationY(translationY);
     }
 
-    /**
-     * Returns whether the menu is displayed on the left side of the view or not.
-     */
-    public boolean isMenuOnLeft() {
-        return mOnLeft;
-    }
-
-    /**
-     * Returns the horizontal space in pixels required to display the menu.
-     */
-    public float getSpaceForMenu() {
-        return mHorizSpaceForIcon * getChildCount();
-    }
-
-    /**
-     * Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is
-     * visible.
-     */
-    public boolean isVisible() {
-        return mAlpha > 0;
-    }
-
-    public void cancelFadeAnimator() {
-        if (mFadeAnimator != null) {
-            mFadeAnimator.cancel();
-        }
-    }
-
-    public void updateMenuAlpha(final float transX, final float size) {
+    @Override
+    public void onTranslationUpdate(float translation) {
+        mTranslation = translation;
         if (mAnimating || !mMenuFadedIn) {
             // Don't adjust when animating, or if the menu hasn't been shown yet.
             return;
         }
-
-        final float fadeThreshold = size * 0.3f;
-        final float absTrans = Math.abs(transX);
+        final float fadeThreshold = mParent.getWidth() * 0.3f;
+        final float absTrans = Math.abs(translation);
         float desiredAlpha = 0;
-
         if (absTrans == 0) {
             desiredAlpha = 0;
         } else if (absTrans <= fadeThreshold) {
             desiredAlpha = 1;
         } else {
-            desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
+            desiredAlpha = 1 - ((absTrans - fadeThreshold) / (mParent.getWidth() - fadeThreshold));
         }
         setMenuAlpha(desiredAlpha);
     }
 
-    public void fadeInMenu(final boolean fromLeft, final float transX,
-            final float notiThreshold) {
+    @Override
+    public void onClick(View v) {
+        if (mMenuListener == null) {
+            // Nothing to do
+            return;
+        }
+        v.getLocationOnScreen(mIconLocation);
+        mParent.getLocationOnScreen(mParentLocation);
+        final int centerX = (int) (mHorizSpaceForIcon / 2);
+        final int centerY = v.getHeight() / 2;
+        final int x = mIconLocation[0] - mParentLocation[0] + centerX;
+        final int y = mIconLocation[1] - mParentLocation[1] + centerY;
+        final int index = mMenuContainer.indexOfChild(v);
+        mMenuListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
+    }
+
+    private boolean isMenuLocationChange() {
+        boolean onLeft = mTranslation > mIconPadding;
+        boolean onRight = mTranslation < -mIconPadding;
+        if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
+            return true;
+        }
+        return false;
+    }
+
+    private void setMenuLocation() {
+        boolean showOnLeft = mTranslation > 0;
+        if ((mIconsPlaced && showOnLeft == mOnLeft) || mSnapping || mParent == null) {
+            // Do nothing
+            return;
+        }
+        final boolean isRtl = mParent.isLayoutRtl();
+        final int count = mMenuContainer.getChildCount();
+        final int width = mParent.getWidth();
+        for (int i = 0; i < count; i++) {
+            final View v = mMenuContainer.getChildAt(i);
+            final float left = isRtl
+                    ? -(width - mHorizSpaceForIcon * (i + 1))
+                    : i * mHorizSpaceForIcon;
+            final float right = isRtl
+                    ? -i * mHorizSpaceForIcon
+                    : width - (mHorizSpaceForIcon * (i + 1));
+            v.setTranslationX(showOnLeft ? left : right);
+        }
+        mOnLeft = showOnLeft;
+        mIconsPlaced = true;
+    }
+
+    private void setMenuAlpha(float alpha) {
+        mAlpha = alpha;
+        if (mMenuContainer == null) {
+            return;
+        }
+        if (alpha == 0) {
+            mMenuFadedIn = false; // Can fade in again once it's gone.
+            mMenuContainer.setVisibility(View.INVISIBLE);
+        } else {
+            mMenuContainer.setVisibility(View.VISIBLE);
+        }
+        final int count = mMenuContainer.getChildCount();
+        for (int i = 0; i < count; i++) {
+            mMenuContainer.getChildAt(i).setAlpha(mAlpha);
+        }
+    }
+
+    /**
+     * Returns the horizontal space in pixels required to display the menu.
+     */
+    private float getSpaceForMenu() {
+        return mHorizSpaceForIcon * mMenuContainer.getChildCount();
+    }
+
+    private final class CheckForDrag implements Runnable {
+        @Override
+        public void run() {
+            final float absTransX = Math.abs(mTranslation);
+            final float bounceBackToMenuWidth = getSpaceForMenu();
+            final float notiThreshold = mParent.getWidth() * 0.4f;
+            if ((!isMenuVisible() || isMenuLocationChange())
+                    && absTransX >= bounceBackToMenuWidth * 0.4
+                    && absTransX < notiThreshold) {
+                fadeInMenu(notiThreshold);
+            }
+        }
+    }
+
+    private void fadeInMenu(final float notiThreshold) {
         if (mDismissing || mAnimating) {
             return;
         }
-        if (isMenuLocationChange(transX)) {
+        if (isMenuLocationChange()) {
             setMenuAlpha(0f);
         }
-        setMenuLocation((int) transX);
+        final float transX = mTranslation;
+        final boolean fromLeft = mTranslation > 0;
+        setMenuLocation();
         mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1);
         mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 final float absTrans = Math.abs(transX);
 
-                boolean pastGear = (fromLeft && transX <= notiThreshold)
+                boolean pastMenu = (fromLeft && transX <= notiThreshold)
                         || (!fromLeft && absTrans <= notiThreshold);
-                if (pastGear && !mMenuFadedIn) {
+                if (pastMenu && !mMenuFadedIn) {
                     setMenuAlpha((float) animation.getAnimatedValue());
                 }
             }
@@ -313,91 +486,77 @@
         mFadeAnimator.start();
     }
 
-    public void updateVerticalLocation() {
-        if (mParent == null || mMenuItems.size() == 0) {
-            return;
-        }
-        int parentHeight = mParent.getCollapsedHeight();
-        float translationY;
-        if (parentHeight < mVertSpaceForIcons) {
-            translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
-        } else {
-            translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
-        }
-        setTranslationY(translationY);
-    }
-
     @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        mIconsPlaced = false;
-        setMenuLocation(mOnLeft ? 1 : -1);
+    public void setMenuItems(ArrayList<MenuItem> items) {
+        // Do nothing we use our own for now.
+        // TODO -- handle / allow custom menu items!
     }
 
-    public void setMenuLocation(int translation) {
-        boolean onLeft = translation > 0;
-        if ((mIconsPlaced && onLeft == mOnLeft) || mSnapping || mParent == null) {
-            // Do nothing
-            return;
+    public static ArrayList<MenuItem> getDefaultMenuItems(Context context) {
+        ArrayList<MenuItem> items = new ArrayList<MenuItem>();
+        Resources res = context.getResources();
+
+        NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(context)
+                .inflate(R.layout.notification_snooze, null, false);
+        String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
+        MenuItem snooze = new NotificationMenuItem(context, snoozeDescription, content,
+                R.drawable.ic_snooze);
+        items.add(snooze);
+
+        String settingsDescription = res.getString(R.string.notification_menu_gear_description);
+        NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate(
+                R.layout.notification_info, null, false);
+        MenuItem settings = new NotificationMenuItem(context, settingsDescription, settingsContent,
+                R.drawable.ic_settings);
+        items.add(settings);
+        return items;
+    }
+
+    private void addMenuView(MenuItem item, ViewGroup parent) {
+        View menuView = item.getMenuView();
+        if (menuView != null) {
+            parent.addView(menuView);
+            menuView.setOnClickListener(this);
+            FrameLayout.LayoutParams lp = (LayoutParams) menuView.getLayoutParams();
+            lp.width = (int) mHorizSpaceForIcon;
+            lp.height = (int) mHorizSpaceForIcon;
+            menuView.setLayoutParams(lp);
         }
-        final boolean isRtl = mParent.isLayoutRtl();
-        final int count = getChildCount();
-        final int width = getWidth();
-        for (int i = 0; i < count; i++) {
-            final View v = getChildAt(i);
-            final float left = isRtl
-                    ? -(width - mHorizSpaceForIcon * (i + 1))
-                    : i * mHorizSpaceForIcon;
-            final float right = isRtl
-                    ? -i * mHorizSpaceForIcon
-                    : width - (mHorizSpaceForIcon * (i + 1));
-            v.setTranslationX(onLeft ? left : right);
+    }
+
+    public static class NotificationMenuItem implements MenuItem {
+        View mMenuView;
+        GutsContent mGutsContent;
+        String mContentDescription;
+
+        public NotificationMenuItem(Context context, String s, GutsContent content, int iconResId) {
+            Resources res = context.getResources();
+            int padding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
+            int tint = res.getColor(R.color.notification_gear_color);
+            AlphaOptimizedImageView iv = new AlphaOptimizedImageView(context);
+            iv.setPadding(padding, padding, padding, padding);
+            Drawable icon = context.getResources().getDrawable(iconResId);
+            iv.setImageDrawable(icon);
+            iv.setColorFilter(tint);
+            iv.setAlpha(1f);
+            mMenuView = iv;
+            mContentDescription = s;
+            mGutsContent = content;
         }
-        mOnLeft = onLeft;
-        mIconsPlaced = true;
-    }
 
-    public boolean isMenuLocationChange(float translation) {
-        boolean onLeft = translation > mIconPadding;
-        boolean onRight = translation < -mIconPadding;
-        if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
-            return true;
+        @Override
+        public View getMenuView() {
+            return mMenuView;
         }
-        return false;
-    }
 
-    public void setDismissing() {
-        mDismissing = true;
-    }
-
-    public void setSnapping(boolean snapping) {
-        mSnapping = snapping;
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (mListener == null) {
-            // Nothing to do
-            return;
+        @Override
+        public View getGutsView() {
+            return mGutsContent.getContentView();
         }
-        v.getLocationOnScreen(mIconLocation);
-        mParent.getLocationOnScreen(mParentLocation);
-        final int centerX = (int) (mHorizSpaceForIcon / 2);
-        final int centerY = (int) (v.getTranslationY() * 2 + v.getHeight()) / 2;
-        final int x = mIconLocation[0] - mParentLocation[0] + centerX;
-        final int y = mIconLocation[1] - mParentLocation[1] + centerY;
-        final int index = indexOfChild(v);
-        mListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
-    }
 
-    @Override
-    public void onPluginConnected(NotificationMenuRowProvider plugin, Context pluginContext) {
-        mMenuProvider = plugin;
-        updateMenu(false /* notify */);
+        @Override
+        public String getContentDescription() {
+            return mContentDescription;
+        }
     }
-
-    @Override
-    public void onPluginDisconnected(NotificationMenuRowProvider plugin) {
-        mMenuProvider = null;
-        updateMenu(false /* notify */);
-    }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index 6b1e62d..0de3e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,14 +16,10 @@
  */
 
 import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -33,23 +29,18 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.RadioGroup;
-import android.widget.RadioGroup.OnCheckedChangeListener;
 import android.widget.TextView;
-import android.widget.Toast;
 import com.android.systemui.R;
 
 public class NotificationSnooze extends LinearLayout
-        implements NotificationMenuRowProvider.SnoozeGutsContent, View.OnClickListener {
+        implements NotificationGuts.GutsContent, View.OnClickListener {
 
     private static final int MAX_ASSISTANT_SUGGESTIONS = 2;
-    private GutsInteractionListener mGutsInteractionListener;
-    private SnoozeListener mSnoozeListener;
+    private NotificationGuts mGutsContainer;
+    private NotificationSwipeActionHelper mSnoozeListener;
     private StatusBarNotification mSbn;
 
     private TextView mSelectedOptionText;
@@ -155,8 +146,8 @@
 
     @Override
     public void onClick(View v) {
-        if (mGutsInteractionListener != null) {
-            mGutsInteractionListener.onInteraction(this);
+        if (mGutsContainer != null) {
+            mGutsContainer.resetFalsingCheck();
         }
         final int id = v.getId();
         final SnoozeOption tag = (SnoozeOption) v.getTag();
@@ -172,7 +163,7 @@
 
     private void undoSnooze() {
         mSelectedOption = null;
-        mGutsInteractionListener.closeGuts(this);
+        mGutsContainer.closeControls(-1 /* x */, -1 /* y */, true /* notify */);
     }
 
     @Override
@@ -185,18 +176,16 @@
         return this;
     }
 
-    @Override
     public void setStatusBarNotification(StatusBarNotification sbn) {
         mSbn = sbn;
     }
 
     @Override
-    public void setInteractionListener(GutsInteractionListener listener) {
-        mGutsInteractionListener = listener;
+    public void setGutsParent(NotificationGuts guts) {
+        mGutsContainer = guts;
     }
 
-    @Override
-    public void setSnoozeListener(SnoozeListener listener) {
+    public void setSnoozeListener(NotificationSwipeActionHelper listener) {
         mSnoozeListener = listener;
     }
 
@@ -206,7 +195,7 @@
         // then we commit the snooze action.
         if (mSnoozeListener != null && mSelectedOption != null) {
             mSnoozing = true;
-            mSnoozeListener.snoozeNotification(mSbn, mSelectedOption);
+            mSnoozeListener.snooze(mSbn, mSelectedOption);
             return true;
         } else {
             // Reset the view once it's closed
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index aa0fcbd..be16266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -82,6 +82,8 @@
         mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
         mSignalClusterView = reinflateSignalCluster(mStatusBar);
         Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
+        // Default to showing until we know otherwise.
+        showSystemIconArea(false);
     }
 
     @Override
@@ -119,6 +121,8 @@
                     .removeView(mNotificationIconAreaInner);
         }
         notificationIconArea.addView(mNotificationIconAreaInner);
+        // Default to showing until we know otherwise.
+        showNotificationIconArea(false);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index ef42b2f..bccc5d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -179,10 +179,6 @@
             setRestingAlpha(
                     anyFingerprintIcon ? 1f : KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT);
             setImageDrawable(icon);
-            String contentDescription = getResources().getString(anyFingerprintIcon
-                    ? R.string.accessibility_unlock_button_fingerprint
-                    : R.string.accessibility_unlock_button);
-            setContentDescription(contentDescription);
             mHasFingerPrintIcon = anyFingerprintIcon;
             if (animation != null && isAnim) {
                 animation.forceAnimationOnUI();
@@ -225,13 +221,13 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         if (mHasFingerPrintIcon) {
-            // Avoid that the button description is also spoken
-            info.setClassName(LockIcon.class.getName());
             AccessibilityNodeInfo.AccessibilityAction unlock
                     = new AccessibilityNodeInfo.AccessibilityAction(
                     AccessibilityNodeInfo.ACTION_CLICK,
                     getContext().getString(R.string.accessibility_unlock_without_fingerprint));
             info.addAction(unlock);
+            info.setHintText(getContext().getString(
+                    R.string.accessibility_waiting_for_fingerprint));
         }
     }
 
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 101aee4..5370ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -140,8 +140,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTileHost;
@@ -241,8 +240,8 @@
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.SystemUI;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeGutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -255,8 +254,8 @@
 
 public class StatusBar extends SystemUI implements DemoMode,
         DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
-        OnHeadsUpChangedListener, VisualStabilityManager.Callback, SnoozeListener,
-        CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
+        OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
+        ActivatableNotificationView.OnActivatedListener,
         ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
         ExpandableNotificationRow.OnExpandClickListener {
     public static final boolean MULTIUSER_DEBUG = false;
@@ -485,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;
@@ -749,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);
@@ -994,16 +987,19 @@
         Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController);
         FragmentHostManager.get(mStatusBarWindow)
                 .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
-                    CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment;
+                    CollapsedStatusBarFragment statusBarFragment =
+                            (CollapsedStatusBarFragment) fragment;
                     statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
                     mStatusBarView = (PhoneStatusBarView) fragment.getView();
                     mStatusBarView.setBar(this);
                     mStatusBarView.setPanel(mNotificationPanel);
                     mStatusBarView.setScrimController(mScrimController);
                     setAreThereNotifications();
+                    checkBarModes();
                 }).getFragmentManager()
                 .beginTransaction()
-                .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG)
+                .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
+                        CollapsedStatusBarFragment.TAG)
                 .commit();
         Dependency.get(StatusBarIconController.class).addIconGroup(
                 new IconManager((ViewGroup) mKeyguardStatusBar.findViewById(R.id.statusIcons)));
@@ -5069,15 +5065,6 @@
 
     }
 
-    public SnoozeListener getSnoozeListener() {
-        return this;
-    }
-
-    @Override
-    public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption) {
-        setNotificationSnoozed(sbn, snoozeOption);
-    }
-
     // Begin Extra BaseStatusBar methods.
 
     protected CommandQueue mCommandQueue;
@@ -5745,7 +5732,7 @@
         }, false /* afterKeyguardGone */);
     }
 
-    protected void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
+    public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
         if (snoozeOption.criterion != null) {
             mNotificationListener.snoozeNotification(sbn.getKey(), snoozeOption.criterion.getId());
         } else {
@@ -5768,20 +5755,22 @@
             mGutsMenuItem = null;
         });
 
-        if (item.gutsContent instanceof SnoozeGutsContent) {
-            ((SnoozeGutsContent) item.gutsContent).setSnoozeListener(getSnoozeListener());
-            ((SnoozeGutsContent) item.gutsContent).setStatusBarNotification(sbn);
-            ((NotificationSnooze) item.gutsContent).setSnoozeOptions(row.getEntry().snoozeCriteria);
+        View gutsView = item.getGutsView();
+        if (gutsView instanceof NotificationSnooze) {
+            NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
+            snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+            snoozeGuts.setStatusBarNotification(sbn);
+            snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
         }
 
-        if (item.gutsContent instanceof NotificationInfo) {
+        if (gutsView instanceof NotificationInfo) {
             final UserHandle userHandle = sbn.getUser();
             PackageManager pmUser = getPackageManagerForUser(mContext,
                     userHandle.getIdentifier());
             final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
                     ServiceManager.getService(Context.NOTIFICATION_SERVICE));
             final String pkg = sbn.getPackageName();
-            NotificationInfo info = (NotificationInfo) item.gutsContent;
+            NotificationInfo info = (NotificationInfo) gutsView;
             final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
                     NotificationChannel channel, int appUid) -> {
                 mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
@@ -5890,7 +5879,7 @@
                                     + "window");
                             return;
                         }
-                        dismissPopups(-1 /* x */, -1 /* y */, false /* resetGear */,
+                        dismissPopups(-1 /* x */, -1 /* y */, false /* resetMenu */,
                                 false /* animate */);
                         guts.setVisibility(View.VISIBLE);
                         final double horz = Math.max(guts.getWidth() - x, x);
@@ -5904,7 +5893,7 @@
                             @Override
                             public void onAnimationEnd(Animator animation) {
                                 super.onAnimationEnd(animation);
-                                // Move the notification view back over the gear
+                                // Move the notification view back over the menu
                                 row.resetTranslation();
                             }
                         });
@@ -5930,19 +5919,19 @@
     }
 
     public void dismissPopups() {
-        dismissPopups(-1 /* x */, -1 /* y */, true /* resetGear */, false /* animate */);
+        dismissPopups(-1 /* x */, -1 /* y */, true /* resetMenu */, false /* animate */);
     }
 
     private void dismissPopups(int x, int y) {
-        dismissPopups(x, y, true /* resetGear */, false /* animate */);
+        dismissPopups(x, y, true /* resetMenu */, false /* animate */);
     }
 
-    public void dismissPopups(int x, int y, boolean resetGear, boolean animate) {
+    public void dismissPopups(int x, int y, boolean resetMenu, boolean animate) {
         if (mNotificationGutsExposed != null) {
             mNotificationGutsExposed.closeControls(x, y, true /* save */);
         }
-        if (resetGear) {
-            mStackScroller.resetExposedGearView(animate, true /* force */);
+        if (resetMenu) {
+            mStackScroller.resetExposedMenuView(animate, true /* force */);
         }
     }
 
@@ -6299,8 +6288,8 @@
                 return;
             }
 
-            // Check if the notification is displaying the gear, if so slide notification back
-            if (row.getSettingsRow() != null && row.getSettingsRow().isVisible()) {
+            // Check if the notification is displaying the menu, if so slide notification back
+            if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
                 row.animateTranslateNotification(0);
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index d3b336b..7d2d0df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -37,6 +37,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
+import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -63,15 +64,15 @@
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationGuts;
-import com.android.systemui.statusbar.NotificationMenuRow;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StackScrollerDecorView;
 import com.android.systemui.statusbar.StatusBarState;
@@ -96,7 +97,7 @@
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        NotificationMenuRowProvider.OnMenuClickListener, ScrollContainer,
+        NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer,
         VisibilityLocationProvider {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
@@ -228,10 +229,9 @@
     private int mMaxScrollAfterExpand;
     private SwipeHelper.LongPressListener mLongPressListener;
 
-    private NotificationMenuRow mCurrIconRow;
+    private NotificationMenuRowPlugin mCurrMenuRow;
     private View mTranslatingParentView;
-    private View mGearExposedView;
-    private boolean mShouldShowGear;
+    private View mMenuExposedView;
 
     /**
      * Should in this touch motion only be scrolling allowed? It's true when the scroller was
@@ -397,7 +397,6 @@
         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
         initView(context);
         mFalsingManager = FalsingManager.getInstance(context);
-        mShouldShowGear = res.getBoolean(R.bool.config_showNotificationGear);
         mShouldDrawNotificationBackground =
                 res.getBoolean(R.bool.config_drawNotificationBackground);
 
@@ -410,6 +409,10 @@
         }
     }
 
+    public NotificationSwipeActionHelper getSwipeActionHelper() {
+        return mSwipeHelper;
+    }
+
     @Override
     public void onMenuClicked(View view, int x, int y, MenuItem item) {
         if (mLongPressListener == null) {
@@ -426,13 +429,22 @@
     @Override
     public void onMenuReset(View row) {
         if (mTranslatingParentView != null && row == mTranslatingParentView) {
-            mSwipeHelper.setSnappedToGear(false);
-            mGearExposedView = null;
+            mMenuExposedView = null;
             mTranslatingParentView = null;
         }
     }
 
     @Override
+    public void onMenuShown(View row) {
+        mMenuExposedView = mTranslatingParentView;
+        if (row instanceof ExpandableNotificationRow) {
+            MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
+                    ((ExpandableNotificationRow) row).getStatusBarNotification()
+                            .getPackageName());
+        }
+        mSwipeHelper.onMenuShown(row);
+    }
+
     protected void onDraw(Canvas canvas) {
         if (mShouldDrawNotificationBackground && mCurrentBounds.top < mCurrentBounds.bottom) {
             canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
@@ -936,9 +948,9 @@
             // We start the swipe and snap back in the same frame, we don't want any animation
             mDragAnimPendingChildren.remove(animView);
         }
-        if (mCurrIconRow != null && targetLeft == 0) {
-            mCurrIconRow.resetState(true /* notify */);
-            mCurrIconRow = null;
+        if (mCurrMenuRow != null && targetLeft == 0) {
+            mCurrMenuRow.resetMenu();
+            mCurrMenuRow = null;
         }
     }
 
@@ -999,10 +1011,10 @@
             ExpandableNotificationRow parent = row.getNotificationParent();
             if (parent != null && parent.areChildrenExpanded()
                     && (parent.areGutsExposed()
-                        || mGearExposedView == parent
+                            || mMenuExposedView == parent
                         || (parent.getNotificationChildren().size() == 1
                                 && parent.isClearable()))) {
-                // In this case the group is expanded and showing the gear for the
+                // In this case the group is expanded and showing the menu for the
                 // group, further interaction should apply to the group, not any
                 // child notifications so we use the parent of the child. We also do the same
                 // if we only have a single child.
@@ -1261,8 +1273,8 @@
 
     public void snapViewIfNeeded(ExpandableNotificationRow child) {
         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
-        // If the child is showing the gear to go to settings, snap to that
-        float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0;
+        // If the child is showing the notification menu snap to that
+        float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0;
         mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
     }
 
@@ -4196,15 +4208,11 @@
         void flingTopOverscroll(float velocity, boolean open);
     }
 
-    private class NotificationSwipeHelper extends SwipeHelper {
-        private static final long SHOW_GEAR_DELAY = 60;
-        private static final long COVER_GEAR_DELAY = 4000;
-        private static final long SWIPE_GEAR_TIMING = 200;
-        private CheckForDrag mCheckForDrag;
+    private class NotificationSwipeHelper extends SwipeHelper
+            implements NotificationSwipeActionHelper {
+        private static final long COVER_MENU_DELAY = 4000;
         private Runnable mFalsingCheck;
         private Handler mHandler;
-        private boolean mGearSnappedTo;
-        private boolean mGearSnappedOnLeft;
 
         public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
             super(swipeDirection, callback, context);
@@ -4212,69 +4220,46 @@
             mFalsingCheck = new Runnable() {
                 @Override
                 public void run() {
-                    resetExposedGearView(true /* animate */, true /* force */);
+                    resetExposedMenuView(true /* animate */, true /* force */);
                 }
             };
         }
 
         @Override
-        public void onDownUpdate(View currView) {
-            // Set the active view
+        public void onDownUpdate(View currView, MotionEvent ev) {
             mTranslatingParentView = currView;
-
-            // Reset check for drag gesture
-            cancelCheckForDrag();
-            if (mCurrIconRow != null) {
-                mCurrIconRow.setSnapping(false);
+            mCurrMenuRow = null;
+            if (mCurrMenuRow != null) {
+                mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */);
             }
-            mCheckForDrag = null;
-            mCurrIconRow = null;
             mHandler.removeCallbacks(mFalsingCheck);
 
-            // Slide back any notifications that might be showing a gear
-            resetExposedGearView(true /* animate */, false /* force */);
+            // Slide back any notifications that might be showing a menu
+            resetExposedMenuView(true /* animate */, false /* force */);
 
             if (currView instanceof ExpandableNotificationRow) {
-                // Set the listener for the current row's gear
-                mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
-                mCurrIconRow.setMenuClickListener(NotificationStackScrollLayout.this);
+                ExpandableNotificationRow row = (ExpandableNotificationRow) currView;
+                mCurrMenuRow = row.createMenu();
+                mCurrMenuRow.setSwipeActionHelper(NotificationSwipeHelper.this);
+                mCurrMenuRow.setMenuClickListener(NotificationStackScrollLayout.this);
             }
         }
 
         @Override
-        public void onMoveUpdate(View view, float translation, float delta) {
+        public void onMoveUpdate(View view, MotionEvent ev, float translation, float delta) {
             mHandler.removeCallbacks(mFalsingCheck);
-
-            if (mCurrIconRow != null) {
-                mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
-
-                // If the gear is visible and the movement is towards it it's not a location change.
-                boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isMenuOnLeft();
-                boolean locationChange = isTowardsGear(translation, onLeft)
-                        ? false : mCurrIconRow.isMenuLocationChange(translation);
-                if (locationChange) {
-                    // Don't consider it "snapped" if location has changed.
-                    setSnappedToGear(false);
-
-                    // Changed directions, make sure we check to fade in icon again.
-                    if (!mHandler.hasCallbacks(mCheckForDrag)) {
-                        // No check scheduled, set null to schedule a new one.
-                        mCheckForDrag = null;
-                    } else {
-                        // Check scheduled, reset alpha and update location; check will fade it in
-                        mCurrIconRow.setMenuAlpha(0f);
-                        mCurrIconRow.setMenuLocation((int) translation);
-                    }
-                }
+            if (mCurrMenuRow != null) {
+                mCurrMenuRow.onTouchEvent(view, ev, 0 /* velocity */);
             }
+        }
 
-            final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
-                    && ((ExpandableNotificationRow) view).areGutsExposed();
-
-            if (mShouldShowGear && !isPinnedHeadsUp(view) && !gutsExposed) {
-                // Only show the gear if we're not a heads up view and guts aren't exposed.
-                checkForDrag();
+        @Override
+        public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
+                float translation) {
+            if (mCurrMenuRow != null) {
+                return mCurrMenuRow.onTouchEvent(animView, ev, velocity);
             }
+            return false;
         }
 
         @Override
@@ -4286,7 +4271,7 @@
                 // of the panel early.
                 handleChildDismissed(view);
             }
-            handleGearCoveredOrDismissed();
+            handleMenuCoveredOrDismissed();
         }
 
         @Override
@@ -4294,121 +4279,21 @@
             super.snapChild(animView, targetLeft, velocity);
             onDragCancelled(animView);
             if (targetLeft == 0) {
-                handleGearCoveredOrDismissed();
-            }
-        }
-
-        private void handleGearCoveredOrDismissed() {
-            cancelCheckForDrag();
-            setSnappedToGear(false);
-            if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
-                mGearExposedView = null;
+                handleMenuCoveredOrDismissed();
             }
         }
 
         @Override
-        public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
-                float translation) {
-            if (mCurrIconRow == null) {
-                cancelCheckForDrag();
-                return false; // Let SwipeHelper handle it.
-            }
-
-            // If the gear icon should not be shown, then there is no need to check if the a swipe
-            // should result in a snapping to the gear icon. As a result, just check if the swipe
-            // was enough to dismiss the notification.
-            if (!mShouldShowGear) {
-                dismissOrSnapBack(animView, velocity, ev);
-                return true;
-            }
-
-            boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isMenuOnLeft());
-            boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
-            final double timeForGesture = ev.getEventTime() - ev.getDownTime();
-            final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView)
-                    && timeForGesture >= SWIPE_GEAR_TIMING;
-
-            if (mGearSnappedTo && mCurrIconRow.isVisible()) {
-                if (mGearSnappedOnLeft == mCurrIconRow.isMenuOnLeft()) {
-                    boolean coveringGear =
-                            Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
-                    if (gestureTowardsGear || coveringGear) {
-                        // Gesture is towards or covering the gear
-                        snapChild(animView, 0 /* leftTarget */, velocity);
-                    } else if (isDismissGesture(ev)) {
-                        // Gesture is a dismiss that's not towards the gear
-                        dismissChild(animView, velocity,
-                                !swipedFastEnough() /* useAccelerateInterpolator */);
-                    } else {
-                        // Didn't move enough to dismiss or cover, snap to the gear
-                        snapToGear(animView, velocity);
-                    }
-                } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
-                        || (gestureTowardsGear && !swipedFarEnough())) {
-                    // The gear has been snapped to previously, however, the gear is now on the
-                    // other side. If gesture is towards gear and not too far snap to the gear.
-                    snapToGear(animView, velocity);
-                } else {
-                    dismissOrSnapBack(animView, velocity, ev);
-                }
-            } else if (((!gestureFastEnough || showGearForSlowOnGoing)
-                    && swipedEnoughToShowGear(animView))
-                    || gestureTowardsGear) {
-                // Gear has not been snapped to previously and this is gear revealing gesture
-                snapToGear(animView, velocity);
-            } else {
-                dismissOrSnapBack(animView, velocity, ev);
-            }
-            return true;
+        public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption) {
+            mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
         }
 
-        private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
-            if (isDismissGesture(ev)) {
-                dismissChild(animView, velocity,
-                        !swipedFastEnough() /* useAccelerateInterpolator */);
-            } else {
-                snapChild(animView, 0 /* leftTarget */, velocity);
+        private void handleMenuCoveredOrDismissed() {
+            if (mMenuExposedView != null && mMenuExposedView == mTranslatingParentView) {
+                mMenuExposedView = null;
             }
         }
 
-        private void snapToGear(View animView, float velocity) {
-            final float snapBackThreshold = getSpaceForGear(animView);
-            final float target = mCurrIconRow.isMenuOnLeft() ? snapBackThreshold
-                    : -snapBackThreshold;
-            mGearExposedView = mTranslatingParentView;
-            if (animView instanceof ExpandableNotificationRow) {
-                MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
-                        ((ExpandableNotificationRow) animView).getStatusBarNotification()
-                                .getPackageName());
-            }
-            if (mCurrIconRow != null) {
-                mCurrIconRow.setSnapping(true);
-                setSnappedToGear(true);
-            }
-            onDragCancelled(animView);
-
-            // If we're on the lockscreen we want to false this.
-            if (isAntiFalsingNeeded()) {
-                mHandler.removeCallbacks(mFalsingCheck);
-                mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
-            }
-            super.snapChild(animView, target, velocity);
-        }
-
-        private boolean swipedEnoughToShowGear(View animView) {
-            if (mTranslatingParentView == null) {
-                return false;
-            }
-            // If the notification can't be dismissed then how far it can move is
-            // restricted -- reduce the distance it needs to move in this case.
-            final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
-            final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
-            final float translation = getTranslation(animView);
-            return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isMenuOnLeft()
-                    ? translation > snapBackThreshold
-                    : translation < -snapBackThreshold);
-        }
-
         @Override
         public Animator getViewTranslationAnimator(View v, float target,
                 AnimatorUpdateListener listener) {
@@ -4429,6 +4314,42 @@
             return ((ExpandableView) v).getTranslation();
         }
 
+        @Override
+        public void dismiss(View animView, float velocity) {
+            dismissChild(animView, velocity,
+                    !swipedFastEnough(0, 0) /* useAccelerateInterpolator */);
+        }
+
+        @Override
+        public void snap(View animView, float targetLeft, float velocity) {
+            snapChild(animView, targetLeft, velocity);
+        }
+
+        @Override
+        public boolean swipedFarEnough(float translation, float viewSize) {
+            return swipedFarEnough();
+        }
+
+        @Override
+        public boolean swipedFastEnough(float translation, float velocity) {
+            return swipedFastEnough();
+        }
+
+        @Override
+        public float getMinDismissVelocity() {
+            return getEscapeVelocity();
+        }
+
+        public void onMenuShown(View animView) {
+            onDragCancelled(animView);
+
+            // If we're on the lockscreen we want to false this.
+            if (isAntiFalsingNeeded()) {
+                mHandler.removeCallbacks(mFalsingCheck);
+                mHandler.postDelayed(mFalsingCheck, COVER_MENU_DELAY);
+            }
+        }
+
         public void closeControlsIfOutsideTouch(MotionEvent ev) {
             NotificationGuts guts = mStatusBar.getExposedGuts();
             View view = null;
@@ -4437,9 +4358,9 @@
                 // Checking guts
                 view = guts;
                 height = guts.getActualHeight();
-            } else if (mCurrIconRow != null && mCurrIconRow.isVisible()
+            } else if (mCurrMenuRow != null && mCurrMenuRow.isMenuVisible()
                     && mTranslatingParentView != null) {
-                // Checking gear
+                // Checking menu
                 view = mTranslatingParentView;
                 height = ((ExpandableView) mTranslatingParentView).getActualHeight();
             }
@@ -4452,95 +4373,29 @@
                 final int y = mTempInt2[1];
                 Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
                 if (!rect.contains(rx, ry)) {
-                    // Touch was outside visible guts / gear notification, close what's visible
-                    mStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
+                    // Touch was outside visible guts / meny notification, close what's visible
+                    mStatusBar.dismissPopups(-1, -1, true /* resetMenu */, true /* animate */);
                 }
             }
         }
 
-        /**
-         * Returns whether the gesture is towards the gear location or not.
-         */
-        private boolean isTowardsGear(float velocity, boolean onLeft) {
-            if (mCurrIconRow == null) {
-                return false;
-            }
-            return mCurrIconRow.isVisible()
-                    && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
-        }
-
-        /**
-         * Indicates the the gear has been snapped to.
-         */
-        private void setSnappedToGear(boolean snapped) {
-            mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isMenuOnLeft() : false;
-            mGearSnappedTo = snapped && mCurrIconRow != null;
-        }
-
-        /**
-         * Returns the horizontal space in pixels required to display the gear behind a
-         * notification.
-         */
-        private float getSpaceForGear(View view) {
-            if (view instanceof ExpandableNotificationRow) {
-                return ((ExpandableNotificationRow) view).getSpaceForGear();
-            }
-            return 0;
-        }
-
-        private void checkForDrag() {
-            if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
-                mCheckForDrag = new CheckForDrag();
-                mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY);
-            }
-        }
-
-        private void cancelCheckForDrag() {
-            if (mCurrIconRow != null) {
-                mCurrIconRow.cancelFadeAnimator();
-            }
-            mHandler.removeCallbacks(mCheckForDrag);
-        }
-
-        private final class CheckForDrag implements Runnable {
-            @Override
-            public void run() {
-                if (mTranslatingParentView == null) {
-                    return;
-                }
-                final float translation = getTranslation(mTranslatingParentView);
-                final float absTransX = Math.abs(translation);
-                final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
-                final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
-                if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
-                        || mCurrIconRow.isMenuLocationChange(translation)))
-                        && absTransX >= bounceBackToGearWidth * 0.4
-                        && absTransX < notiThreshold) {
-                    // Fade in the gear
-                    mCurrIconRow.fadeInMenu(translation > 0 /* fromLeft */, translation,
-                            notiThreshold);
-                }
-            }
-        }
-
-        public void resetExposedGearView(boolean animate, boolean force) {
-            if (mGearExposedView == null
-                    || (!force && mGearExposedView == mTranslatingParentView)) {
-                // If no gear is showing or it's showing for this view we do nothing.
+        public void resetExposedMenuView(boolean animate, boolean force) {
+            if (mMenuExposedView == null
+                    || (!force && mMenuExposedView == mTranslatingParentView)) {
+                // If no menu is showing or it's showing for this view we do nothing.
                 return;
             }
-            final View prevGearExposedView = mGearExposedView;
+            final View prevMenuExposedView = mMenuExposedView;
             if (animate) {
-                Animator anim = getViewTranslationAnimator(prevGearExposedView,
+                Animator anim = getViewTranslationAnimator(prevMenuExposedView,
                         0 /* leftTarget */, null /* updateListener */);
                 if (anim != null) {
                     anim.start();
                 }
-            } else if (mGearExposedView instanceof ExpandableNotificationRow) {
-                ((ExpandableNotificationRow) mGearExposedView).resetTranslation();
+            } else if (mMenuExposedView instanceof ExpandableNotificationRow) {
+                ((ExpandableNotificationRow) mMenuExposedView).resetTranslation();
             }
-            mGearExposedView = null;
-            mGearSnappedTo = false;
+            mMenuExposedView = null;
         }
     }
 
@@ -4557,8 +4412,8 @@
         }
     }
 
-    public void resetExposedGearView(boolean animate, boolean force) {
-        mSwipeHelper.resetExposedGearView(animate, force);
+    public void resetExposedMenuView(boolean animate, boolean force) {
+        mSwipeHelper.resetExposedMenuView(animate, force);
     }
 
     public void closeControlsIfOutsideTouch(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index ca53bc4..8ed4fca 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -24,7 +24,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.volume.VolumeDialogController.State;
+import com.android.systemui.plugins.VolumeDialogController.State;
 
 import java.util.Arrays;
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 9d0ecec..2f9bcff 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -24,13 +24,19 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
 
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.plugins.PluginDependency;
+import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.VolumeDialog;
+import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 
@@ -41,7 +47,7 @@
  * Implementation of VolumeComponent backed by the new volume dialog.
  */
 public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
-        VolumeDialogController.UserActivityListener{
+        VolumeDialogControllerImpl.UserActivityListener{
 
     public static final String VOLUME_DOWN_SILENT = "sysui_volume_down_silent";
     public static final String VOLUME_UP_SILENT = "sysui_volume_up_silent";
@@ -53,9 +59,8 @@
 
     private final SystemUI mSysui;
     private final Context mContext;
-    private final VolumeDialogController mController;
-    private final ZenModeController mZenModeController;
-    private final VolumeDialog mDialog;
+    private final VolumeDialogControllerImpl mController;
+    private VolumeDialog mDialog;
     private VolumePolicy mVolumePolicy = new VolumePolicy(
             DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT,  // volumeDownToEnterSilent
             DEFAULT_VOLUME_UP_TO_EXIT_SILENT,  // volumeUpToExitSilent
@@ -66,16 +71,35 @@
     public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler) {
         mSysui = sysui;
         mContext = context;
-        mController = SystemUIFactory.getInstance().createVolumeDialogController(context, null);
+        mController = (VolumeDialogControllerImpl) Dependency.get(VolumeDialogController.class);
         mController.setUserActivityListener(this);
-        mZenModeController = Dependency.get(ZenModeController.class);
-        mDialog = new VolumeDialog(context, WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY,
-                mController, mZenModeController, mVolumeDialogCallback);
+        // Allow plugins to reference the VolumeDialogController.
+        Dependency.get(PluginDependencyProvider.class)
+                .allowPluginDependency(VolumeDialogController.class);
+        Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
+                .withPlugin(VolumeDialog.class)
+                .withDefault(this::createDefault)
+                .withCallback(dialog -> {
+                    if (mDialog != null) {
+                        mDialog.destroy();
+                    }
+                    mDialog = dialog;
+                    mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
+                }).build();
         applyConfiguration();
         Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
                 VOLUME_SILENT_DO_NOT_DISTURB);
     }
 
+    private VolumeDialog createDefault() {
+        VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
+        impl.setStreamImportant(AudioManager.STREAM_ALARM, true);
+        impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
+        impl.setAutomute(true);
+        impl.setSilentMode(false);
+        return impl;
+    }
+
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (VOLUME_DOWN_SILENT.equals(key)) {
@@ -118,10 +142,6 @@
     }
 
     private void applyConfiguration() {
-        mDialog.setStreamImportant(AudioManager.STREAM_ALARM, true);
-        mDialog.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
-        mDialog.setAutomute(true);
-        mDialog.setSilentMode(false);
         mController.setVolumePolicy(mVolumePolicy);
         mController.showDndTile(true);
     }
@@ -149,8 +169,6 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mController.dump(fd, pw, args);
-        mDialog.dump(pw);
     }
 
     private void startSettings(Intent intent) {
@@ -158,7 +176,7 @@
                 true /* onlyProvisioned */, true /* dismissShade */);
     }
 
-    private final VolumeDialog.Callback mVolumeDialogCallback = new VolumeDialog.Callback() {
+    private final VolumeDialogImpl.Callback mVolumeDialogCallback = new VolumeDialogImpl.Callback() {
         @Override
         public void onZenSettingsClicked() {
             startSettings(ZenModePanel.ZEN_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
rename to packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 276b7c3..5d51a33 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.volume;
 
-import android.annotation.IntegerRes;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -44,10 +43,11 @@
 import android.service.notification.Condition;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
 
 import java.io.FileDescriptor;
@@ -63,8 +63,8 @@
  *
  *  Methods ending in "W" must be called on the worker thread.
  */
-public class VolumeDialogController {
-    private static final String TAG = Util.logTag(VolumeDialogController.class);
+public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
+    private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class);
 
     private static final int DYNAMIC_STREAM_START_INDEX = 100;
     private static final int VIBRATE_HINT_DURATION = 50;
@@ -89,7 +89,6 @@
     private final Context mContext;
     private AudioManager mAudio;
     private final NotificationManager mNoMan;
-    private final ComponentName mComponent;
     private final SettingObserver mObserver;
     private final Receiver mReceiver = new Receiver();
     private final MediaSessions mMediaSessions;
@@ -108,11 +107,10 @@
 
     protected final VC mVolumeController = new VC();
 
-    public VolumeDialogController(Context context, ComponentName component) {
+    public VolumeDialogControllerImpl(Context context) {
         mContext = context.getApplicationContext();
         Events.writeEvent(mContext, Events.EVENT_COLLECTION_STARTED);
-        mComponent = component;
-        mWorkerThread = new HandlerThread(VolumeDialogController.class.getSimpleName());
+        mWorkerThread = new HandlerThread(VolumeDialogControllerImpl.class.getSimpleName());
         mWorkerThread.start();
         mWorker = new W(mWorkerThread.getLooper());
         mMediaSessions = createMediaSessions(mContext, mWorkerThread.getLooper(),
@@ -197,7 +195,7 @@
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println(VolumeDialogController.class.getSimpleName() + " state:");
+        pw.println(VolumeDialogControllerImpl.class.getSimpleName() + " state:");
         pw.print("  mDestroyed: "); pw.println(mDestroyed);
         pw.print("  mVolumePolicy: "); pw.println(mVolumePolicy);
         pw.print("  mState: "); pw.println(mState.toString(4));
@@ -530,7 +528,7 @@
     }
 
     private final class VC extends IVolumeController.Stub {
-        private final String TAG = VolumeDialogController.TAG + ".VC";
+        private final String TAG = VolumeDialogControllerImpl.TAG + ".VC";
 
         @Override
         public void displaySafeVolumeWarning(int flags) throws RemoteException {
@@ -958,113 +956,6 @@
         }
     }
 
-    public static final class StreamState {
-        public boolean dynamic;
-        public int level;
-        public int levelMin;
-        public int levelMax;
-        public boolean muted;
-        public boolean muteSupported;
-        public @IntegerRes int name;
-        public String remoteLabel;
-        public boolean routedToBluetooth;
-
-        public StreamState copy() {
-            final StreamState rt = new StreamState();
-            rt.dynamic = dynamic;
-            rt.level = level;
-            rt.levelMin = levelMin;
-            rt.levelMax = levelMax;
-            rt.muted = muted;
-            rt.muteSupported = muteSupported;
-            rt.name = name;
-            rt.remoteLabel = remoteLabel;
-            rt.routedToBluetooth = routedToBluetooth;
-            return rt;
-        }
-    }
-
-    public static final class State {
-        public static int NO_ACTIVE_STREAM = -1;
-
-        public final SparseArray<StreamState> states = new SparseArray<StreamState>();
-
-        public int ringerModeInternal;
-        public int ringerModeExternal;
-        public int zenMode;
-        public ComponentName effectsSuppressor;
-        public String effectsSuppressorName;
-        public int activeStream = NO_ACTIVE_STREAM;
-
-        public State copy() {
-            final State rt = new State();
-            for (int i = 0; i < states.size(); i++) {
-                rt.states.put(states.keyAt(i), states.valueAt(i).copy());
-            }
-            rt.ringerModeExternal = ringerModeExternal;
-            rt.ringerModeInternal = ringerModeInternal;
-            rt.zenMode = zenMode;
-            if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone();
-            rt.effectsSuppressorName = effectsSuppressorName;
-            rt.activeStream = activeStream;
-            return rt;
-        }
-
-        @Override
-        public String toString() {
-            return toString(0);
-        }
-
-        public String toString(int indent) {
-            final StringBuilder sb = new StringBuilder("{");
-            if (indent > 0) sep(sb, indent);
-            for (int i = 0; i < states.size(); i++) {
-                if (i > 0) {
-                    sep(sb, indent);
-                }
-                final int stream = states.keyAt(i);
-                final StreamState ss = states.valueAt(i);
-                sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level)
-                        .append('[').append(ss.levelMin).append("..").append(ss.levelMax)
-                        .append(']');
-                if (ss.muted) sb.append(" [MUTED]");
-                if (ss.dynamic) sb.append(" [DYNAMIC]");
-            }
-            sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal);
-            sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal);
-            sep(sb, indent); sb.append("zenMode:").append(zenMode);
-            sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor);
-            sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
-            sep(sb, indent); sb.append("activeStream:").append(activeStream);
-            if (indent > 0) sep(sb, indent);
-            return sb.append('}').toString();
-        }
-
-        private static void sep(StringBuilder sb, int indent) {
-            if (indent > 0) {
-                sb.append('\n');
-                for (int i = 0; i < indent; i++) {
-                    sb.append(' ');
-                }
-            } else {
-                sb.append(',');
-            }
-        }
-    }
-
-    public interface Callbacks {
-        void onShowRequested(int reason);
-        void onDismissRequested(int reason);
-        void onStateChanged(State state);
-        void onLayoutDirectionChanged(int layoutDirection);
-        void onConfigurationChanged();
-        void onShowVibrateHint();
-        void onShowSilentHint();
-        void onScreenOff();
-        void onShowSafetyWarning(int flags);
-        void onAccessibilityModeChanged(Boolean showA11yStream);
-    }
-
     public interface UserActivityListener {
         void onUserActivity();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
rename to packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1933349a..697cac9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.volume;
 
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.animation.ObjectAnimator;
 import android.annotation.NonNull;
@@ -72,28 +75,27 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.plugins.VolumeDialogController.State;
+import com.android.systemui.plugins.VolumeDialogController.StreamState;
+import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerZenModePanel;
-import com.android.systemui.volume.VolumeDialogController.State;
-import com.android.systemui.volume.VolumeDialogController.StreamState;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
-
 /**
  * Visual presentation of the volume dialog.
  *
- * A client of VolumeDialogController and its state model.
+ * A client of VolumeDialogControllerImpl and its state model.
  *
  * Methods ending in "H" must be called on the (ui) handler.
  */
-public class VolumeDialog implements TunerService.Tunable {
-    private static final String TAG = Util.logTag(VolumeDialog.class);
+public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
+    private static final String TAG = Util.logTag(VolumeDialogImpl.class);
 
     public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
 
@@ -102,7 +104,7 @@
 
     private final Context mContext;
     private final H mHandler = new H();
-    private final VolumeDialogController mController;
+    private VolumeDialogController mController;
 
     private Window mWindow;
     private CustomDialog mDialog;
@@ -123,7 +125,7 @@
     private final ColorStateList mActiveSliderTint;
     private final ColorStateList mInactiveSliderTint;
     private VolumeDialogMotion mMotion;
-    private final int mWindowType;
+    private int mWindowType;
     private final ZenModeController mZenModeController;
 
     private boolean mShowing;
@@ -146,32 +148,39 @@
     private boolean mShowFullZen;
     private TunerZenModePanel mZenPanel;
 
-    public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
-            ZenModeController zenModeController, Callback callback) {
+    public VolumeDialogImpl(Context context) {
         mContext = context;
-        mController = controller;
-        mCallback = callback;
-        mWindowType = windowType;
-        mZenModeController = zenModeController;
-        mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
-        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mZenModeController = Dependency.get(ZenModeController.class);
+        mController = Dependency.get(VolumeDialogController.class);
+        mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mAccessibilityMgr =
                 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
         mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
         mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
+    }
+
+    public void init(int windowType, Callback callback) {
+        mCallback = callback;
+        mWindowType = windowType;
 
         initDialog();
 
         mAccessibility.init();
 
-        controller.addCallback(mControllerCallbackH, mHandler);
-        controller.getState();
+        mController.addCallback(mControllerCallbackH, mHandler);
+        mController.getState();
         Dependency.get(TunerService.class).addTunable(this, SHOW_FULL_ZEN);
 
         final Configuration currentConfig = mContext.getResources().getConfiguration();
         mDensity = currentConfig.densityDpi;
     }
 
+    @Override
+    public void destroy() {
+        mController.removeCallback(mControllerCallbackH);
+    }
+
     private void initDialog() {
         mDialog = new CustomDialog(mContext);
 
@@ -193,7 +202,7 @@
         final WindowManager.LayoutParams lp = mWindow.getAttributes();
         lp.type = mWindowType;
         lp.format = PixelFormat.TRANSLUCENT;
-        lp.setTitle(VolumeDialog.class.getSimpleName());
+        lp.setTitle(VolumeDialogImpl.class.getSimpleName());
         lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
         lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
         lp.gravity = Gravity.TOP;
@@ -361,7 +370,7 @@
     }
 
     public void dump(PrintWriter writer) {
-        writer.println(VolumeDialog.class.getSimpleName() + " state:");
+        writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
         writer.print("  mShowing: "); writer.println(mShowing);
         writer.print("  mExpanded: "); writer.println(mExpanded);
         writer.print("  mExpandButtonAnimationRunning: ");
@@ -459,10 +468,6 @@
         }
     }
 
-    public void destroy() {
-        mController.removeCallback(mControllerCallbackH);
-    }
-
     public void show(int reason) {
         mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
     }
@@ -1289,9 +1294,4 @@
         private int animTargetProgress;
         private int lastAudibleLevel = 1;
     }
-
-    public interface Callback {
-        void onZenSettingsClicked();
-        void onZenPrioritySettingsClicked();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
index a2c32b7..d28e42e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/car/CarVolumeDialogController.java
@@ -25,7 +25,7 @@
 import android.os.IBinder;
 import android.util.Log;
 
-import com.android.systemui.volume.VolumeDialogController;
+import com.android.systemui.volume.VolumeDialogControllerImpl;
 
 /**
  * A volume dialog controller for the automotive use case.
@@ -33,7 +33,7 @@
  * {@link android.car.media.CarAudioManager} is the source of truth to get the stream volumes.
  * And volume changes should be sent to the car's audio module instead of the android's audio mixer.
  */
-public class CarVolumeDialogController extends VolumeDialogController {
+public class CarVolumeDialogController extends VolumeDialogControllerImpl {
     private static final String TAG = "CarVolumeDialogController";
 
     private final Car mCar;
@@ -57,8 +57,8 @@
         }
     };
 
-    public CarVolumeDialogController(Context context, ComponentName component) {
-        super(context, component);
+    public CarVolumeDialogController(Context context) {
+        super(context);
         mCar = Car.createCar(context, mConnection);
         mCar.connect();
     }
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/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
index c2c6336..31b9bae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
@@ -18,6 +18,8 @@
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.ViewUtils;
+import android.testing.ViewUtils;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
@@ -35,10 +37,11 @@
 
     @Test
     public void testAttachDetach() {
-        NotificationMenuRow row = new NotificationMenuRow(mContext);
-        ViewUtils.attachView(row);
+        NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+        row.createMenu(null);
+        ViewUtils.attachView(row.getMenuView());
         TestableLooper.get(this).processAllMessages();
-        ViewUtils.detachView(row);
+        ViewUtils.detachView(row.getMenuView());
         TestableLooper.get(this).processAllMessages();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index a9acda3..6ddbffc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -16,6 +16,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -104,6 +105,6 @@
 
         fragment.disable(0, 0, false);
 
-        Mockito.verify(mNotificationAreaInner).setVisibility(eq(View.VISIBLE));
+        Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
     }
 }
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index f516d74..8808988 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -20,9 +20,7 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothProfile;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
 
 import com.android.settingslib.bluetooth.BluetoothEventManager;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -33,13 +31,10 @@
 
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
 public class BluetoothControllerImplTest extends SysuiTestCase {
 
     private LocalBluetoothManager mMockBluetoothManager;
@@ -52,7 +47,7 @@
 
     @Before
     public void setup() throws Exception {
-        mTestableLooper = TestableLooper.get(this);
+        mTestableLooper = new TestableLooper();
         mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
         mDevices = new ArrayList<>();
         mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
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 776fa1e..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,17 +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;
@@ -33,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;
@@ -60,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();
@@ -70,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;
             }
         });
     }
@@ -107,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);
     }
 
     /**
@@ -133,34 +125,19 @@
     }
 
     /**
-     * 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) {
-        LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
+            @Nullable String filterText, @NonNull String packageName) {
+        if (DEBUG) {
+            Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + filterText);
+        }
+        final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
                 .setPackageName(packageName)
                 .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
                         filterText == null ? 0 : filterText.length())
@@ -173,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);
@@ -205,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 85eecdf..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);
@@ -100,11 +100,12 @@
             final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
             content.measure(widthMeasureSpec, heightMeasureSpec);
             content.setOnClickListener(v -> mCallback.onResponsePicked(response));
+            content.setElevation(context.getResources().getDimension(R.dimen.floating_window_z));
             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);
@@ -138,8 +139,7 @@
             };
 
             final LayoutInflater inflater = LayoutInflater.from(context);
-            mListView = (ListView) inflater.inflate(
-                    com.android.internal.R.layout.autofill_dataset_picker, null);
+            mListView = (ListView) inflater.inflate(R.layout.autofill_dataset_picker, null);
             mListView.setAdapter(mAdapter);
             mListView.setOnItemClickListener((adapter, view, position, id) -> {
                 final ViewItem vi = mAdapter.getItem(position);
@@ -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,72 +344,9 @@
             }
             return false;
         }
-
-        public void show(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 d312902..099f557 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -13,6 +13,7 @@
     ../../../../system/netd/server/binder/android/net/INetd.aidl \
     ../../../../system/netd/server/binder/android/net/metrics/INetdEventListener.aidl \
     ../../../native/cmds/installd/binder/android/os/IInstalld.aidl \
+    ../../../native/services/vr/vr_window_manager/aidl/android/service/vr/IVrWindowManager.aidl \
 
 LOCAL_AIDL_INCLUDES += \
     system/netd/server/binder
@@ -21,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/media/java/android/media/UnsupportedCasException.java b/services/core/java/com/android/server/NetworkManagementInternal.java
similarity index 60%
rename from media/java/android/media/UnsupportedCasException.java
rename to services/core/java/com/android/server/NetworkManagementInternal.java
index 3167637..f53c454 100644
--- a/media/java/android/media/UnsupportedCasException.java
+++ b/services/core/java/com/android/server/NetworkManagementInternal.java
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.server;
 
 /**
- * Exception thrown when an attempt is made to construct a MediaCas object
- * using a CA_system_id that is not supported by the device
+ * NetworkManagement local system service interface.
+ *
+ * @hide Only for use within the system server.
  */
-public final class UnsupportedCasException extends MediaCasException {
-    public UnsupportedCasException(String detailMessage) {
-        super(detailMessage);
-    }
+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 8e6310f..457c5f8 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -97,6 +97,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IMediaContainerService;
 import com.android.internal.os.AppFuseMount;
+import com.android.internal.os.FuseAppLoop;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
@@ -350,7 +351,7 @@
     private int mNextAppFuseName = 0;
 
     @GuardedBy("mAppFuseLock")
-    private final SparseArray<Integer> mAppFusePids = new SparseArray<>();
+    private AppFuseBridge mAppFuseBridge = null;
 
     private VolumeInfo findVolumeByIdOrThrow(String id) {
         synchronized (mLock) {
@@ -714,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;
@@ -2078,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
@@ -2991,124 +3007,79 @@
         }
     }
 
-    class CloseableHolder<T extends AutoCloseable> implements AutoCloseable {
-        @Nullable T mCloseable;
-
-        CloseableHolder(T closeable) {
-            mCloseable = closeable;
+    private ParcelFileDescriptor mountAppFuse(int uid, int mountId)
+            throws NativeDaemonConnectorException {
+        final NativeDaemonEvent event = StorageManagerService.this.mConnector.execute(
+                "appfuse", "mount", uid, Process.myPid(), mountId);
+        if (event.getFileDescriptors() == null ||
+            event.getFileDescriptors().length == 0) {
+            throw new NativeDaemonConnectorException("Cannot obtain device FD");
         }
-
-        @Nullable T get() {
-            return mCloseable;
-        }
-
-        @Nullable T release() {
-            final T result = mCloseable;
-            mCloseable = null;
-            return result;
-        }
-
-        @Override
-        public void close() {
-            if (mCloseable != null) {
-                IoUtils.closeQuietly(mCloseable);
-            }
-        }
+        return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
     }
 
-    class AppFuseMountScope implements AppFuseBridge.IMountScope {
-        final int mUid;
-        final int mName;
-        final ParcelFileDescriptor mDeviceFd;
-
-        AppFuseMountScope(int uid, int pid, int name) throws NativeDaemonConnectorException {
-            final NativeDaemonEvent event = mConnector.execute(
-                    "appfuse", "mount", uid, Process.myPid(), name);
-            mUid = uid;
-            mName = name;
-            synchronized (mLock) {
-                mAppFusePids.put(name, pid);
-            }
-            if (event.getFileDescriptors() != null &&
-                    event.getFileDescriptors().length > 0) {
-                mDeviceFd = new ParcelFileDescriptor(event.getFileDescriptors()[0]);
-            } else {
-                mDeviceFd = null;
-            }
+    class AppFuseMountScope extends AppFuseBridge.MountScope {
+        public AppFuseMountScope(int uid, int pid, int mountId)
+                throws NativeDaemonConnectorException {
+            super(uid, pid, mountId, mountAppFuse(uid, mountId));
         }
 
         @Override
-        public void close() throws NativeDaemonConnectorException {
-            try {
-                IoUtils.closeQuietly(mDeviceFd);
-                mConnector.execute(
-                        "appfuse", "unmount", mUid, Process.myPid(), mName);
-            } finally {
-                synchronized (mLock) {
-                    mAppFusePids.delete(mName);
-                }
-            }
-        }
-
-        @Override
-        public ParcelFileDescriptor getDeviceFileDescriptor() {
-            return mDeviceFd;
+        public void close() throws Exception {
+            super.close();
+            mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
         }
     }
 
     @Override
     public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+        Slog.v(TAG, "mountProxyFileDescriptorBridge");
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
-        final int name;
-        synchronized (mAppFuseLock) {
-            name = mNextAppFuseName++;
-        }
-        try (CloseableHolder<AppFuseMountScope> mountScope =
-                new CloseableHolder<>(new AppFuseMountScope(uid, pid, name))) {
-            if (mountScope.get().getDeviceFileDescriptor() == null) {
-                throw new RemoteException("Failed to obtain device FD");
-            }
 
-            // Create communication channel.
-            final ArrayBlockingQueue<Boolean> channel = new ArrayBlockingQueue<>(1);
-            final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair();
-            try (CloseableHolder<ParcelFileDescriptor> remote = new CloseableHolder<>(fds[0])) {
-                new Thread(
-                        new AppFuseBridge(mountScope.release(), fds[1], channel),
-                        AppFuseBridge.TAG).start();
-                if (!channel.take()) {
-                    throw new RemoteException("Failed to init AppFuse mount point");
+        while (true) {
+            synchronized (mAppFuseLock) {
+                boolean newlyCreated = false;
+                if (mAppFuseBridge == null) {
+                    mAppFuseBridge = new AppFuseBridge();
+                    new Thread(mAppFuseBridge, AppFuseBridge.TAG).start();
+                    newlyCreated = true;
                 }
-
-                return new AppFuseMount(name, remote.release());
+                try {
+                    final int name = mNextAppFuseName++;
+                    try {
+                        return new AppFuseMount(
+                            name,
+                            mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name)));
+                    } catch (AppFuseBridge.BridgeException e) {
+                        if (newlyCreated) {
+                            // If newly created bridge fails, it's a real error.
+                            throw new RemoteException(e.getMessage());
+                        }
+                        // It seems the thread of mAppFuseBridge has already been terminated.
+                        mAppFuseBridge = null;
+                    }
+                } catch (NativeDaemonConnectorException e) {
+                    throw e.rethrowAsParcelableException();
+                }
             }
-        } catch (NativeDaemonConnectorException e){
-            throw e.rethrowAsParcelableException();
-        } catch (IOException | InterruptedException error) {
-            throw new RemoteException(error.getMessage());
         }
     }
 
     @Override
-    public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode) {
-        final int uid = Binder.getCallingUid();
+    public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode)
+            throws RemoteException {
+        Slog.v(TAG, "mountProxyFileDescriptorBridge");
         final int pid = Binder.getCallingPid();
         try {
             synchronized (mAppFuseLock) {
-                final int expectedPid = mAppFusePids.get(mountId, -1);
-                if (expectedPid == -1) {
-                    Slog.i(TAG, "The mount point has already been unmounted");
-                    return null;
+                if (mAppFuseBridge == null) {
+                    throw new RemoteException("Cannot find mount point");
                 }
-                if (expectedPid != pid) {
-                    throw new SecurityException("Mount point was not created by this process.");
-                }
+                return mAppFuseBridge.openFile(pid, mountId, fileId, mode);
             }
-            return AppFuseBridge.openFile(uid, mountId, fileId, mode);
-        } catch (FileNotFoundException error) {
-            Slog.e(TAG, "Failed to openProxyFileDescriptor", error);
-            return null;
+        } catch (FileNotFoundException | SecurityException | InterruptedException error) {
+            throw new RemoteException(error.getMessage());
         }
     }
 
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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 8e3e3ea..77a02b4 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -202,16 +202,17 @@
                 new HashMap<Account, Integer>();
         final Object cacheLock = new Object();
         /** protected by the {@link #cacheLock} */
-        final HashMap<String, Account[]> accountCache =
-                new LinkedHashMap<>();
+        final HashMap<String, Account[]> accountCache = new LinkedHashMap<>();
         /** protected by the {@link #cacheLock} */
         private final Map<Account, Map<String, String>> userDataCache = new HashMap<>();
         /** protected by the {@link #cacheLock} */
         private final Map<Account, Map<String, String>> authTokenCache = new HashMap<>();
         /** protected by the {@link #cacheLock} */
         private final TokenCache accountTokenCaches = new TokenCache();
+        /** protected by the {@link #cacheLock} */
+        private final Map<Account, Map<String, Integer>> visibilityCache = new HashMap<>();
 
-        /** protected by the {@link #mReceiversForType}
+        /** protected by the {@link #mReceiversForType},
          *  type -> (packageName -> number of active receivers)
          *  type == null is used to get notifications about all account types
          */
@@ -524,25 +525,29 @@
                     String.format("uid %s cannot get secrets for account %s", callingUid, account);
             throw new SecurityException(msg);
         }
-        return getPackagesAndVisibilityForAccount(account, accounts);
+        synchronized (accounts.cacheLock) {
+            return getPackagesAndVisibilityForAccountLocked(account, accounts);
+        }
     }
 
     /**
-     * Returns all package names and visibility values, which were set for given account.
+     * Returns Map with all package names and visibility values for given account.
+     * The method and returned map must be guarded by accounts.cacheLock
      *
      * @param account Account to get visibility values.
      * @param accounts UserAccount that currently hosts the account and application
      *
-     * @return Map from package names to visibility.
+     * @return Map with cache for package names to visibility.
      */
-    private Map<String, Integer> getPackagesAndVisibilityForAccount(Account account,
+    private @NonNull Map<String, Integer> getPackagesAndVisibilityForAccountLocked(Account account,
             UserAccounts accounts) {
-        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
-        try {
-            return accounts.accountsDb.findAllVisibilityValuesForAccount(account);
-        } finally {
-            StrictMode.setThreadPolicy(oldPolicy);
+        Map<String, Integer> accountVisibility = accounts.visibilityCache.get(account);
+        if (accountVisibility == null) {
+            Log.d(TAG, "Visibility was not initialized");
+            accountVisibility = new HashMap<>();
+            accounts.visibilityCache.put(account, accountVisibility);
         }
+        return accountVisibility;
     }
 
     @Override
@@ -572,14 +577,13 @@
      * @return Visibility value, AccountManager.VISIBILITY_UNDEFINED if no value was stored.
      *
      */
-    private int getAccountVisibility(Account account, String packageName, UserAccounts accounts) {
-        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
-        try {
-            Integer visibility =
-                accounts.accountsDb.findAccountVisibility(account, packageName);
+    private int getAccountVisibilityFromCache(Account account, String packageName,
+            UserAccounts accounts) {
+        synchronized (accounts.cacheLock) {
+            Map<String, Integer> accountVisibility =
+                getPackagesAndVisibilityForAccountLocked(account, accounts);
+            Integer visibility = accountVisibility.get(packageName);
             return visibility != null ? visibility : AccountManager.VISIBILITY_UNDEFINED;
-        } finally {
-            StrictMode.setThreadPolicy(oldPolicy);
         }
     }
 
@@ -595,9 +599,7 @@
      */
     private Integer resolveAccountVisibility(Account account, @NonNull String packageName,
             UserAccounts accounts) {
-
         Preconditions.checkNotNull(packageName, "packageName cannot be null");
-
         int uid = -1;
         try {
             long identityToken = clearCallingIdentity();
@@ -630,7 +632,7 @@
         }
 
         // Return stored value if it was set.
-        int visibility = getAccountVisibility(account, packageName, accounts);
+        int visibility = getAccountVisibilityFromCache(account, packageName, accounts);
 
         if (AccountManager.VISIBILITY_UNDEFINED != visibility) {
             return visibility;
@@ -652,13 +654,13 @@
                 || canReadContacts || isPrivileged) {
             // Use legacy for preO apps with GET_ACCOUNTS permission or pre/postO with signature
             // match.
-            visibility = getAccountVisibility(account,
+            visibility = getAccountVisibilityFromCache(account,
                     AccountManager.PACKAGE_NAME_KEY_LEGACY_VISIBLE, accounts);
             if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
                 visibility = AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
             }
         } else {
-            visibility = getAccountVisibility(account,
+            visibility = getAccountVisibilityFromCache(account,
                     AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, accounts);
             if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
                 visibility = AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE;
@@ -751,21 +753,10 @@
                 packagesToVisibility = new HashMap<>();
             }
 
-            final long accountId = accounts.accountsDb.findDeAccountId(account);
-            if (accountId < 0) {
+            if (!updateAccountVisibilityLocked(account, packageName, newVisibility, accounts)) {
                 return false;
             }
 
-            final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
-            try {
-                if (!accounts.accountsDb.setAccountVisibility(accountId, packageName,
-                        newVisibility)) {
-                    return false;
-                }
-            } finally {
-                StrictMode.setThreadPolicy(oldPolicy);
-            }
-
             if (notify) {
                 for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
                     if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
@@ -778,6 +769,29 @@
         }
     }
 
+    // Update account visibility in cache and database.
+    private boolean updateAccountVisibilityLocked(Account account, String packageName,
+            int newVisibility, UserAccounts accounts) {
+        final long accountId = accounts.accountsDb.findDeAccountId(account);
+        if (accountId < 0) {
+            return false;
+        }
+
+        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+        try {
+            if (!accounts.accountsDb.setAccountVisibility(accountId, packageName,
+                    newVisibility)) {
+                return false;
+            }
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+        Map<String, Integer> accountVisibility =
+            getPackagesAndVisibilityForAccountLocked(account, accounts);
+        accountVisibility.put(packageName, newVisibility);
+        return true;
+    }
+
     @Override
     public void registerAccountListener(String[] accountTypes, String opPackageName) {
         int callingUid = Binder.getCallingUid();
@@ -1042,9 +1056,12 @@
                         accounts.userDataCache.remove(account);
                         accounts.authTokenCache.remove(account);
                         accounts.accountTokenCaches.remove(account);
+                        accounts.visibilityCache.remove(account);
 
-                        for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
-                            if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
+                        for (Entry<String, Integer> packageToVisibility :
+                                packagesToVisibility.entrySet()) {
+                            if (packageToVisibility.getValue()
+                                    != AccountManager.VISIBILITY_NOT_VISIBLE) {
                                 notifyPackage(packageToVisibility.getKey(), accounts);
                             }
                         }
@@ -1067,6 +1084,7 @@
                     }
                     accounts.accountCache.put(accountType, accountsForType);
                 }
+                accounts.visibilityCache.putAll(accountsDb.findAllVisibilityValues());
             } finally {
                 if (accountDeleted) {
                     sendAccountsChangedBroadcast(accounts.userId);
@@ -1181,19 +1199,31 @@
     }
 
     private void removeVisibilityValuesForPackage(String packageName) {
+        if (isSpecialPackageKey(packageName)) {
+            return;
+        }
         synchronized (mUsers) {
-          for (int i = 0; i < mUsers.size(); i++) {
-              UserAccounts accounts = mUsers.valueAt(i);
-              try {
-                  int uid = mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
-              } catch (NameNotFoundException e) {
-                  // package does not exist - remove visibility values
-                  accounts.accountsDb.deleteAccountVisibilityForPackage(packageName);
+            int numberOfUsers = mUsers.size();
+            for (int i = 0; i < numberOfUsers; i++) {
+                UserAccounts accounts = mUsers.valueAt(i);
+                try {
+                    mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
+                } catch (NameNotFoundException e) {
+                    // package does not exist - remove visibility values
+                    accounts.accountsDb.deleteAccountVisibilityForPackage(packageName);
+                    synchronized(accounts.cacheLock) {
+                        for (Account account : accounts.visibilityCache.keySet()) {
+                            Map<String, Integer> accountVisibility =
+                                getPackagesAndVisibilityForAccountLocked(account, accounts);
+                            accountVisibility.remove(packageName);
+                        }
+                    }
               }
           }
         }
     }
 
+
     private void onCleanupUser(int userId) {
         Log.i(TAG, "onCleanupUser " + userId);
         UserAccounts accounts;
@@ -1849,6 +1879,7 @@
              */
             Map<String, String> tmpData = accounts.userDataCache.get(accountToRename);
             Map<String, String> tmpTokens = accounts.authTokenCache.get(accountToRename);
+            Map<String, Integer> tmpVisibility = accounts.visibilityCache.get(accountToRename);
             removeAccountFromCacheLocked(accounts, accountToRename);
             /*
              * Update the cached data associated with the renamed
@@ -1856,6 +1887,7 @@
              */
             accounts.userDataCache.put(renamedAccount, tmpData);
             accounts.authTokenCache.put(renamedAccount, tmpTokens);
+            accounts.visibilityCache.put(renamedAccount, tmpVisibility);
             accounts.previousNameCache.put(
                     renamedAccount,
                     new AtomicReference<>(accountToRename.name));
@@ -5369,6 +5401,7 @@
         accounts.userDataCache.remove(account);
         accounts.authTokenCache.remove(account);
         accounts.previousNameCache.remove(account);
+        accounts.visibilityCache.remove(account);
     }
 
     /**
diff --git a/services/core/java/com/android/server/accounts/AccountsDb.java b/services/core/java/com/android/server/accounts/AccountsDb.java
index 22543cb..8ca7ea1 100644
--- a/services/core/java/com/android/server/accounts/AccountsDb.java
+++ b/services/core/java/com/android/server/accounts/AccountsDb.java
@@ -909,7 +909,7 @@
     }
 
     Integer findAccountVisibility(Account account, String packageName) {
-        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
         final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
                 SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_PACKAGE + "=? ",
                 new String[] {account.name, account.type, packageName}, null, null, null);
@@ -924,7 +924,7 @@
     }
 
     Integer findAccountVisibility(long accountId, String packageName) {
-        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
         final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
                 VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_PACKAGE + "=? ",
                 new String[] {String.valueOf(accountId), packageName}, null, null, null);
@@ -972,6 +972,41 @@
         return result;
     }
 
+    /**
+     * Returns a map account -> (package -> visibility)
+     */
+    Map <Account, Map<String, Integer>> findAllVisibilityValues() {
+        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+        Map<Account, Map<String, Integer>> result = new HashMap<>();
+        Cursor cursor = db.rawQuery(
+                "SELECT " + TABLE_VISIBILITY + "." + VISIBILITY_PACKAGE
+                        + ", " + TABLE_VISIBILITY + "." + VISIBILITY_VALUE
+                        + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
+                        + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE
+                        + " FROM " + TABLE_VISIBILITY
+                        + " JOIN " + TABLE_ACCOUNTS
+                        + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
+                        + " = " + TABLE_VISIBILITY + "." + VISIBILITY_ACCOUNTS_ID, null);
+        try {
+            while (cursor.moveToNext()) {
+                String packageName = cursor.getString(0);
+                Integer visibility = cursor.getInt(1);
+                String accountName = cursor.getString(2);
+                String accountType = cursor.getString(3);
+                Account account = new Account(accountName, accountType);
+                Map <String, Integer> accountVisibility = result.get(account);
+                if (accountVisibility == null) {
+                    accountVisibility = new HashMap<>();
+                    result.put(account, accountVisibility);
+                }
+                accountVisibility.put(packageName, visibility);
+            }
+        } finally {
+            cursor.close();
+        }
+        return result;
+    }
+
     boolean deleteAccountVisibilityForPackage(String packageName) {
         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
         return db.delete(TABLE_VISIBILITY, VISIBILITY_PACKAGE + "=? ",
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3437512..7a83436 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();
@@ -2735,6 +2743,7 @@
     @VisibleForTesting
     public ActivityManagerService(Injector injector) {
         mInjector = injector;
+        mContext = mInjector.getContext();
         GL_ES_VERSION = 0;
         mActivityStarter = null;
         mAppErrors = null;
@@ -5460,11 +5469,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 +5490,7 @@
             return null;
         }
 
-        dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
+        dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativePids);
         return tracesFile;
     }
 
@@ -5522,7 +5532,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 +5554,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 +13816,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 +13873,7 @@
                 mFullscreenThumbnailScale = res.getFraction(
                     com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
             }
+            mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
         }
     }
 
@@ -19032,26 +19043,13 @@
                     break;
                 case android.hardware.Camera.ACTION_NEW_PICTURE:
                 case android.hardware.Camera.ACTION_NEW_VIDEO:
-                    // These broadcasts are no longer allowed by the system, since they can
-                    // cause significant thrashing at a crictical point (using the camera).
-                    // Apps should use JobScehduler to monitor for media provider changes.
-                    Slog.w(TAG, action + " no longer allowed; dropping from "
-                            + UserHandle.formatUid(callingUid));
-                    if (resultTo != null) {
-                        final BroadcastQueue queue = broadcastQueueForIntent(intent);
-                        try {
-                            queue.performReceiveLocked(callerApp, resultTo, intent,
-                                    Activity.RESULT_CANCELED, null, null,
-                                    false, false, userId);
-                        } catch (RemoteException e) {
-                            Slog.w(TAG, "Failure ["
-                                    + queue.mQueueName + "] sending broadcast result of "
-                                    + intent, e);
-
-                        }
-                    }
-                    // Lie; we don't want to crash the app.
-                    return ActivityManager.BROADCAST_SUCCESS;
+                    // In N we just turned these off; in O we are turing them back on partly,
+                    // only for registered receivers.  This will still address the main problem
+                    // (a spam of apps waking up when a picture is taken putting significant
+                    // memory pressure on the system at a bad point), while still allowing apps
+                    // that are already actively running to know about this happening.
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    break;
                 case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
                     mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
                     break;
@@ -22529,6 +22527,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) {
@@ -23571,10 +23572,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);
@@ -23869,6 +23874,12 @@
 
     @VisibleForTesting
     public static class Injector {
+        private NetworkManagementInternal mNmi;
+
+        public Context getContext() {
+            return null;
+        }
+
         public AppOpsService getAppOpsService(File file, Handler handler) {
             return new AppOpsService(file, handler);
         }
@@ -23878,8 +23889,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..c10f77c 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 = nativeProcs == null ? null : 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..bff5f51 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -617,15 +617,15 @@
         boolean kept = true;
         try {
             final ActivityRecord r = topRunningActivityLocked();
-            final boolean wasFocused = supervisor.isFocusedStack(sourceStack)
+            final boolean wasFocused = r != null && supervisor.isFocusedStack(sourceStack)
                     && (topRunningActivityLocked() == r);
-            final boolean wasResumed = sourceStack.mResumedActivity == r;
-            final boolean wasPaused = sourceStack.mPausingActivity == r;
+            final boolean wasResumed = r != null && sourceStack.mResumedActivity == r;
+            final boolean wasPaused = r != null && sourceStack.mPausingActivity == r;
 
             // In some cases the focused stack isn't the front stack. E.g. pinned stack.
             // Whenever we are moving the top activity from the front stack we want to make sure to
             // move the stack to the front.
-            final boolean wasFront = supervisor.isFrontStackOnDisplay(sourceStack)
+            final boolean wasFront = r != null && supervisor.isFrontStackOnDisplay(sourceStack)
                     && (sourceStack.topRunningActivityLocked() == r);
 
             // Adjust the position for the new parent stack as needed.
@@ -667,8 +667,10 @@
             // new stack by moving the stack to the front.
             final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
                     || (moveStackMode == REPARENT_KEEP_STACK_AT_FRONT && (wasFocused || wasFront));
-            toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed, wasPaused,
-                    reason);
+            if (r != null) {
+                toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed,
+                        wasPaused, reason);
+            }
             if (!animate) {
                 toStack.mNoAnimActivities.add(topActivity);
             }
@@ -924,12 +926,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 +946,7 @@
     }
 
     // Close up recents linked list.
-    void closeRecentsChain() {
+    private void closeRecentsChain() {
         if (mPrevAffiliate != null) {
             mPrevAffiliate.setNextAffiliate(mNextAffiliate);
         }
@@ -1188,7 +1190,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 +2000,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 +2013,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 +2035,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 +2056,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/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index f953a36..67b80f6 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -87,6 +87,7 @@
 
     public UidRecord(int _uid) {
         uid = _uid;
+        idle = true;
         reset();
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 333d27b..c139fa8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6555,7 +6555,7 @@
 
     public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
         final boolean isPrivileged =
-                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                         android.Manifest.permission.MODIFY_AUDIO_ROUTING));
         mPlaybackMonitor.registerPlaybackCallback(pcdb, isPrivileged);
     }
@@ -6566,7 +6566,7 @@
 
     public List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() {
         final boolean isPrivileged =
-                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                         android.Manifest.permission.MODIFY_AUDIO_ROUTING));
         return mPlaybackMonitor.getActivePlaybackConfigurations(isPrivileged);
     }
diff --git a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
new file mode 100644
index 0000000..c6dc11c
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
@@ -0,0 +1,201 @@
+/*
+ * 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.media;
+
+import android.content.Context;
+import android.media.AudioManager.AudioPlaybackCallback;
+import android.media.AudioPlaybackConfiguration;
+import android.media.IAudioService;
+import android.media.IPlaybackConfigDispatcher;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Monitors changes in audio playback and notify the newly started audio playback through the
+ * {@link OnAudioPlaybackStartedListener}.
+ */
+class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
+    private static boolean DEBUG = MediaSessionService.DEBUG;
+    private static String TAG = "AudioPlaybackMonitor";
+
+    /**
+     * Called when audio playback is started for a given UID.
+     */
+    interface OnAudioPlaybackStartedListener {
+        void onAudioPlaybackStarted(int uid);
+    }
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final OnAudioPlaybackStartedListener mListener;
+
+    private Set<Integer> mActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>();
+    private Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
+
+    // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
+    // The UID whose audio playback becomes active at the last comes first.
+    // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
+    private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
+
+    AudioPlaybackMonitor(Context context, IAudioService audioService,
+            OnAudioPlaybackStartedListener listener) {
+        mContext = context;
+        mListener = listener;
+        try {
+            audioService.registerPlaybackCallback(this);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Failed to register playback callback", e);
+        }
+    }
+
+    /**
+     * Called when the {@link AudioPlaybackConfiguration} is updated.
+     * <p>If an app starts audio playback, the app's local media session will be the media button
+     * session. If the app has multiple media sessions, the playback active local session will be
+     * picked.
+     *
+     * @param configs List of the current audio playback configuration
+     */
+    @Override
+    public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            Set<Integer> newActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>();
+            List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
+            synchronized (mLock) {
+                mActiveAudioPlaybackClientUids.clear();
+                for (AudioPlaybackConfiguration config : configs) {
+                    // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
+                    // (i.e. playback from the SoundPool class which is only for sound effects)
+                    // playback.
+                    // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
+                    // specific audio/video players.
+                    if (!config.isActive()
+                            || config.getPlayerType()
+                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+                        continue;
+                    }
+                    mActiveAudioPlaybackClientUids.add(config.getClientUid());
+
+                    newActiveAudioPlaybackPlayerInterfaceIds.add(config.getPlayerInterfaceId());
+                    if (!mActiveAudioPlaybackPlayerInterfaceIds.contains(
+                            config.getPlayerInterfaceId())) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Found a new active media playback. " +
+                                    AudioPlaybackConfiguration.toLogFriendlyString(config));
+                        }
+                        // New active audio playback.
+                        newActiveAudioPlaybackClientUids.add(config.getClientUid());
+                        int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
+                        if (index == 0) {
+                            // It's the lastly played music app already. Skip updating.
+                            continue;
+                        } else if (index > 0) {
+                            mSortedAudioPlaybackClientUids.remove(index);
+                        }
+                        mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
+                    }
+                }
+                mActiveAudioPlaybackPlayerInterfaceIds.clear();
+                mActiveAudioPlaybackPlayerInterfaceIds = newActiveAudioPlaybackPlayerInterfaceIds;
+            }
+            for (int uid : newActiveAudioPlaybackClientUids) {
+                mListener.onAudioPlaybackStarted(uid);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
+     * audio/video) The UID whose audio playback becomes active at the last comes first.
+     */
+    public IntArray getSortedAudioPlaybackClientUids() {
+        IntArray sortedAudioPlaybackClientUids = new IntArray();
+        synchronized (mLock) {
+            sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
+        }
+        return sortedAudioPlaybackClientUids;
+    }
+
+    /**
+     * Returns if the audio playback is active for the uid.
+     */
+    public boolean isPlaybackActive(int uid) {
+        synchronized (mLock) {
+            return mActiveAudioPlaybackClientUids.contains(uid);
+        }
+    }
+
+    /**
+     * Cleans up the sorted list of audio playback client UIDs with given {@param
+     * mediaButtonSessionUid}.
+     * <p>UIDs whose audio playback started after the media button session's audio playback
+     * cannot be the lastly played media app. So they won't needed anymore.
+     *
+     * @param mediaButtonSessionUid UID of the media button session.
+     */
+    public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
+        synchronized (mLock) {
+            int userId = UserHandle.getUserId(mediaButtonSessionUid);
+            for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
+                if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
+                    break;
+                }
+                if (userId == UserHandle.getUserId(mSortedAudioPlaybackClientUids.get(i))) {
+                    // Clean up unnecessary UIDs.
+                    // It doesn't need to be managed profile aware because it's just to prevent
+                    // the list from increasing indefinitely. The media button session updating
+                    // shouldn't be affected by cleaning up.
+                    mSortedAudioPlaybackClientUids.remove(i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Dumps {@link AudioPlaybackMonitor}.
+     */
+    public void dump(PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.println(prefix + "Audio playback (lastly played comes first)");
+            String indent = prefix + "  ";
+            for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
+                int uid = mSortedAudioPlaybackClientUids.get(i);
+                pw.print(indent + "uid=" + uid + " packages=");
+                String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+                if (packages != null && packages.length > 0) {
+                    for (int j = 0; j < packages.length; j++) {
+                        pw.print(packages[j] + " ");
+                    }
+                }
+                pw.println();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 20663a0..7f75c83 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -66,12 +66,6 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     /**
-     * The length of time a session will still be considered active after
-     * pausing in ms.
-     */
-    private static final int ACTIVE_BUFFER = 30000;
-
-    /**
      * The amount of time we'll send an assumed volume after the last volume
      * command before reverting to the last reported volume.
      */
@@ -109,7 +103,6 @@
     private int mRatingType;
     private int mRepeatMode;
     private boolean mShuffleModeEnabled;
-    private long mLastActiveTime;
     // End TransportPerformer fields
 
     // Volume handling fields
@@ -130,7 +123,7 @@
     private String mCallingPackage;
 
     public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
-            ISessionCallback cb, String tag, MediaSessionService service, Handler handler) {
+            ISessionCallback cb, String tag, MediaSessionService service, Looper handlerLooper) {
         mOwnerPid = ownerPid;
         mOwnerUid = ownerUid;
         mUserId = userId;
@@ -140,7 +133,7 @@
         mSession = new SessionStub();
         mSessionCb = new SessionCb(cb);
         mService = service;
-        mHandler = new MessageHandler(handler.getLooper());
+        mHandler = new MessageHandler(handlerLooper);
         mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
         mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
         mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
@@ -211,6 +204,15 @@
     }
 
     /**
+     * Get the UID this session was created for.
+     *
+     * @return The UID for this session.
+     */
+    public int getUid() {
+        return mOwnerUid;
+    }
+
+    /**
      * Get the user id this session was created for.
      *
      * @return The user id for this session.
@@ -244,7 +246,7 @@
     public void adjustVolume(int direction, int flags, String packageName, int uid,
             boolean useSuggested) {
         int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
-        if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
+        if (isPlaybackActive() || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
         if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
@@ -320,25 +322,13 @@
     }
 
     /**
-     * Check if the session is currently performing playback. This will also
-     * return true if the session was recently paused.
+     * Check if the session is currently performing playback.
      *
-     * @param includeRecentlyActive True if playback that was recently paused
-     *            should count, false if it shouldn't.
      * @return True if the session is performing playback, false otherwise.
      */
-    public boolean isPlaybackActive(boolean includeRecentlyActive) {
-        int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
-        if (MediaSession.isActiveState(state)) {
-            return true;
-        }
-        if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
-            long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
-            if (inactiveTime < ACTIVE_BUFFER) {
-                return true;
-            }
-        }
-        return false;
+    public boolean isPlaybackActive() {
+        int state = mPlaybackState == null ? PlaybackState.STATE_NONE : mPlaybackState.getState();
+        return MediaSession.isActiveState(state);
     }
 
     /**
@@ -456,7 +446,7 @@
 
     @Override
     public String toString() {
-        return mPackageName + "/" + mTag + " (uid=" + mUserId + ")";
+        return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
     }
 
     private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
@@ -817,6 +807,12 @@
         @Override
         public void setMediaButtonReceiver(PendingIntent pi) {
             mMediaButtonReceiver = pi;
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
@@ -842,15 +838,19 @@
 
         @Override
         public void setPlaybackState(PlaybackState state) {
-            int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
-            int newState = state == null ? 0 : state.getState();
-            if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) {
-                mLastActiveTime = SystemClock.elapsedRealtime();
-            }
+            int oldState = mPlaybackState == null
+                    ? PlaybackState.STATE_NONE : mPlaybackState.getState();
+            int newState = state == null
+                    ? PlaybackState.STATE_NONE : state.getState();
             synchronized (mLock) {
                 mPlaybackState = state;
             }
-            mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mService.onSessionPlaystateChanged(MediaSessionRecord.this, oldState, newState);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
             mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index ea9128f..4bf9d8f 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -57,11 +57,13 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.speech.RecognizerIntent;
 import android.text.TextUtils;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -85,7 +87,7 @@
  */
 public class MediaSessionService extends SystemService implements Monitor {
     private static final String TAG = "MediaSessionService";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     // Leave log for key event always.
     private static final boolean DEBUG_KEY_EVENT = true;
 
@@ -114,6 +116,7 @@
     // It's always not null after the MediaSessionService is started.
     private FullUserRecord mCurrentFullUserRecord;
     private MediaSessionRecord mGlobalPrioritySession;
+    private AudioPlaybackMonitor mAudioPlaybackMonitor;
 
     // Used to notify system UI when remote volume was changed. TODO find a
     // better way to handle this.
@@ -134,6 +137,19 @@
         mKeyguardManager =
                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
         mAudioService = getAudioService();
+        mAudioPlaybackMonitor = new AudioPlaybackMonitor(getContext(), mAudioService,
+                new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
+                    @Override
+                    public void onAudioPlaybackStarted(int uid) {
+                        synchronized (mLock) {
+                            FullUserRecord user =
+                                    getFullUserRecordLocked(UserHandle.getUserId(uid));
+                            if (user != null) {
+                                user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+                            }
+                        }
+                    }
+                });
         mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
         mContentResolver = getContext().getContentResolver();
         mSettingsObserver = new SettingsObserver();
@@ -161,9 +177,10 @@
             user.mPriorityStack.onSessionStateChange(record);
             if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
                 mGlobalPrioritySession = record;
+                user.pushAddressedPlayerChangedLocked();
             }
+            mHandler.postSessionsChanged(record.getUserId());
         }
-        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
     }
 
     /**
@@ -180,18 +197,14 @@
         }
     }
 
-    public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
-        boolean updateSessions = false;
+    public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (user == null || !user.mPriorityStack.contains(record)) {
                 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
                 return;
             }
-            updateSessions = user.mPriorityStack.onPlaystateChange(record, oldState, newState);
-        }
-        if (updateSessions) {
-            mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
+            user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
         }
     }
 
@@ -332,6 +345,9 @@
         }
         if (mGlobalPrioritySession == session) {
             mGlobalPrioritySession = null;
+            if (session.isActive() && user != null) {
+                user.pushAddressedPlayerChangedLocked();
+            }
         }
 
         try {
@@ -340,8 +356,7 @@
             // ignore exceptions while destroying a session.
         }
         session.onDestroy();
-
-        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
+        mHandler.postSessionsChanged(session.getUserId());
     }
 
     private void enforcePackageName(String packageName, int uid) {
@@ -461,7 +476,7 @@
         }
 
         final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
-                callerPackageName, cb, tag, this, mHandler);
+                callerPackageName, cb, tag, this, mHandler.getLooper());
         try {
             cb.asBinder().linkToDeath(session, 0);
         } catch (RemoteException e) {
@@ -469,8 +484,7 @@
         }
 
         user.addSessionLocked(session);
-
-        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
+        mHandler.postSessionsChanged(userId);
 
         if (DEBUG) {
             Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
@@ -496,10 +510,6 @@
             }
             List<MediaSessionRecord> records = user.mPriorityStack.getActiveSessions(userId);
             int size = records.size();
-            if (size > 0 && records.get(0).isPlaybackActive(false)) {
-                user.rememberMediaButtonReceiverLocked(records.get(0));
-            }
-            user.pushAddressedPlayerChangedLocked();
             ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
             for (int i = 0; i < size; i++) {
                 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
@@ -536,6 +546,22 @@
         }
     }
 
+    /**
+     * Called when the media button receiver for the {@param record} is changed.
+     *
+     * @param record the media session whose media button receiver is updated.
+     */
+    public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+            MediaSessionRecord mediaButtonSession =
+                    user.mPriorityStack.getMediaButtonSession();
+            if (record == mediaButtonSession) {
+                user.rememberMediaButtonReceiverLocked(mediaButtonSession);
+            }
+        }
+    }
+
     private String getCallingPackageName(int uid) {
         String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
         if (packages != null && packages.length > 0) {
@@ -568,10 +594,10 @@
      * place makes more sense and increases the readability.</p>
      * <p>The contents of this object is guarded by {@link #mLock}.
      */
-    final class FullUserRecord {
+    final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
         private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
         private final int mFullUserId;
-        private final MediaSessionStack mPriorityStack = new MediaSessionStack();
+        private final MediaSessionStack mPriorityStack;
         private PendingIntent mLastMediaButtonReceiver;
         private ComponentName mRestoredMediaButtonReceiver;
         private int mRestoredMediaButtonReceiverUserId;
@@ -588,6 +614,7 @@
 
         public FullUserRecord(int fullUserId) {
             mFullUserId = fullUserId;
+            mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
             // Restore the remembered media button receiver before the boot.
             String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
                     Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
@@ -603,15 +630,14 @@
         }
 
         public void destroySessionsForUserLocked(int userId) {
-            List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, 0, userId);
+            List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
             for (MediaSessionRecord session : sessions) {
                 MediaSessionService.this.destroySessionLocked(session);
             }
         }
 
         public void addSessionLocked(MediaSessionRecord session) {
-            mPriorityStack.addSession(session,
-                    mFullUserId == mFullUserIds.get(session.getUserId()));
+            mPriorityStack.addSession(session);
         }
 
         public void removeSessionLocked(MediaSessionRecord session) {
@@ -642,21 +668,41 @@
             mPriorityStack.dump(pw, indent);
         }
 
-        // Remember the media button receiver and keep it in the persistent storage.
-        private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
+        @Override
+        public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
+                MediaSessionRecord newMediaButtonSession) {
+            if (DEBUG_KEY_EVENT) {
+                Log.d(TAG, "Media button session will be changed to " + newMediaButtonSession);
+            }
+            synchronized (mLock) {
+                if (oldMediaButtonSession != null) {
+                    mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
+                }
+                if (newMediaButtonSession != null) {
+                    rememberMediaButtonReceiverLocked(newMediaButtonSession);
+                    mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
+                }
+                pushAddressedPlayerChangedLocked();
+            }
+        }
+
+        // Remember media button receiver and keep it in the persistent storage.
+        public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
             PendingIntent receiver = record.getMediaButtonReceiver();
-            if (receiver == null) {
-                return;
-            }
             mLastMediaButtonReceiver = receiver;
-            ComponentName component = receiver.getIntent().getComponent();
-            if (component != null && record.getPackageName().equals(component.getPackageName())) {
-                String componentName = component.flattenToString();
-                Settings.Secure.putStringForUser(mContentResolver,
-                        Settings.System.MEDIA_BUTTON_RECEIVER,
-                        componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
-                        mFullUserId);
+            mRestoredMediaButtonReceiver = null;
+            String componentName = "";
+            if (receiver != null) {
+                ComponentName component = receiver.getIntent().getComponent();
+                if (component != null
+                        && record.getPackageName().equals(component.getPackageName())) {
+                    componentName = component.flattenToString();
+                }
             }
+            Settings.Secure.putStringForUser(mContentResolver,
+                    Settings.System.MEDIA_BUTTON_RECEIVER,
+                    componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
+                    mFullUserId);
         }
 
         private void pushAddressedPlayerChangedLocked() {
@@ -682,14 +728,8 @@
         }
 
         private MediaSessionRecord getMediaButtonSessionLocked() {
-            if (isGlobalPriorityActiveLocked()) {
-                return mGlobalPrioritySession;
-            }
-            // If we don't have a media button receiver to fall back on
-            // include non-playing sessions for dispatching.
-            boolean useNotPlayingSessions = (mLastMediaButtonReceiver == null
-                    && mRestoredMediaButtonReceiver == null);
-            return mPriorityStack.getDefaultMediaButtonSession(useNotPlayingSessions);
+            return isGlobalPriorityActiveLocked()
+                    ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
         }
     }
 
@@ -1262,6 +1302,7 @@
                 for (int i = 0; i < count; i++) {
                     mUserRecords.valueAt(i).dumpLocked(pw, "");
                 }
+                mAudioPlaybackMonitor.dump(pw, "");
             }
         }
 
@@ -1390,7 +1431,7 @@
                         PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
                         if (DEBUG_KEY_EVENT) {
                             Log.d(TAG, "Sending " + keyEvent
-                                    + " to the last known pendingIntent " + receiver);
+                                    + " to the last known PendingIntent " + receiver);
                         }
                         receiver.send(getContext(),
                                 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
@@ -1413,7 +1454,8 @@
                         }
                         mediaButtonIntent.setComponent(receiver);
                         getContext().sendBroadcastAsUser(mediaButtonIntent,
-                                UserHandle.of(mCurrentFullUserRecord.mRestoredMediaButtonReceiverUserId));
+                                UserHandle.of(mCurrentFullUserRecord
+                                      .mRestoredMediaButtonReceiverUserId));
                         if (mCurrentFullUserRecord.mCallback != null) {
                             mCurrentFullUserRecord.mCallback
                                     .onMediaKeyEventDispatchedToMediaButtonReceiver(
@@ -1426,23 +1468,6 @@
                 } catch (RemoteException e) {
                     Log.w(TAG, "Failed to send callback", e);
                 }
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG, "Sending media key ordered broadcast");
-                }
-                if (needWakeLock) {
-                    mMediaEventWakeLock.acquire();
-                }
-                // Fallback to legacy behavior
-                Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
-                keyIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-                if (needWakeLock) {
-                    keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
-                }
-                // Send broadcast only to the full user.
-                getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.CURRENT,
-                        null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
             }
         }
 
@@ -1637,12 +1662,13 @@
     final class MessageHandler extends Handler {
         private static final int MSG_SESSIONS_CHANGED = 1;
         private static final int MSG_VOLUME_INITIAL_DOWN = 2;
+        private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SESSIONS_CHANGED:
-                    pushSessionsChanged(msg.arg1);
+                    pushSessionsChanged((int) msg.obj);
                     break;
                 case MSG_VOLUME_INITIAL_DOWN:
                     synchronized (mLock) {
@@ -1657,8 +1683,15 @@
             }
         }
 
-        public void post(int what, int arg1, int arg2) {
-            obtainMessage(what, arg1, arg2).sendToTarget();
+        public void postSessionsChanged(int userId) {
+            // Use object instead of the arguments when posting message to remove pending requests.
+            Integer userIdInteger = mIntegerCache.get(userId);
+            if (userIdInteger == null) {
+                userIdInteger = Integer.valueOf(userId);
+                mIntegerCache.put(userId, userIdInteger);
+            }
+            removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
+            obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
         }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 8b80734..b0d8adc 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -16,12 +16,13 @@
 
 package com.android.server.media;
 
-import android.app.ActivityManager;
 import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.PlaybackState;
 import android.media.session.MediaSession;
-import android.os.RemoteException;
+import android.media.session.PlaybackState;
+import android.os.Debug;
 import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -33,6 +34,20 @@
  * <p>This class isn't thread-safe. The caller should take care of the synchronization.
  */
 class MediaSessionStack {
+    private static final boolean DEBUG = MediaSessionService.DEBUG;
+    private static final String TAG = "MediaSessionStack";
+
+    /**
+     * Listens the change in the media button session.
+     */
+    interface OnMediaButtonSessionChangedListener {
+        /**
+         * Called when the media button session is changed.
+         */
+        void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
+                MediaSessionRecord newMediaButtonSession);
+    }
+
     /**
      * These are states that usually indicate the user took an action and should
      * bump priority regardless of the old state.
@@ -51,57 +66,45 @@
             PlaybackState.STATE_CONNECTING,
             PlaybackState.STATE_PLAYING };
 
-    private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+    /**
+     * Sorted list of the media sessions.
+     * The session of which PlaybackState is changed to ALWAYS_PRIORITY_STATES or
+     * TRANSITION_PRIORITY_STATES comes first.
+     * @see #shouldUpdatePriority
+     */
+    private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
 
-    // The last record that either entered one of the playing states or was
-    // added.
-    private MediaSessionRecord mLastInterestingRecord;
-    private MediaSessionRecord mCachedButtonReceiver;
+    private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
+
+    /**
+     * The media button session which receives media key events.
+     * It could be null if the previous media buttion session is released.
+     */
+    private MediaSessionRecord mMediaButtonSession;
+
     private MediaSessionRecord mCachedDefault;
     private MediaSessionRecord mCachedVolumeDefault;
     private ArrayList<MediaSessionRecord> mCachedActiveList;
-    private ArrayList<MediaSessionRecord> mCachedTransportControlList;
 
-    /**
-     * Checks if a media session is created from the most recent app.
-     *
-     * @param record A media session record to be examined.
-     * @return {@code true} if the media session's package name equals to the most recent app, false
-     *            otherwise.
-     */
-    private static boolean isFromMostRecentApp(MediaSessionRecord record) {
-        try {
-            List<ActivityManager.RecentTaskInfo> tasks =
-                    ActivityManager.getService().getRecentTasks(1,
-                            ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS |
-                            ActivityManager.RECENT_IGNORE_UNAVAILABLE |
-                            ActivityManager.RECENT_INCLUDE_PROFILES |
-                            ActivityManager.RECENT_WITH_EXCLUDED, record.getUserId()).getList();
-            if (tasks != null && !tasks.isEmpty()) {
-                ActivityManager.RecentTaskInfo recentTask = tasks.get(0);
-                if (recentTask.userId == record.getUserId() && recentTask.baseIntent != null) {
-                    return recentTask.baseIntent.getComponent().getPackageName()
-                            .equals(record.getPackageName());
-                }
-            }
-        } catch (RemoteException e) {
-            return false;
-        }
-        return false;
+    MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) {
+        mAudioPlaybackMonitor = monitor;
+        mOnMediaButtonSessionChangedListener = listener;
     }
 
     /**
      * Add a record to the priority tracker.
      *
      * @param record The record to add.
-     * @param fromForegroundUser {@code true} if the session is created by the foreground user.
      */
-    public void addSession(MediaSessionRecord record, boolean fromForegroundUser) {
+    public void addSession(MediaSessionRecord record) {
         mSessions.add(record);
         clearCache();
-        if (fromForegroundUser && isFromMostRecentApp(record)) {
-            mLastInterestingRecord = record;
-        }
+
+        // Update the media button session.
+        // The added session could be the session from the package with the audio playback.
+        // This can happen if an app starts audio playback before creating media session.
+        updateMediaButtonSessionIfNeeded();
     }
 
     /**
@@ -111,6 +114,11 @@
      */
     public void removeSession(MediaSessionRecord record) {
         mSessions.remove(record);
+        if (mMediaButtonSession == record) {
+            // When the media button session is gone, try to find the alternative media session
+            // in the media button session app.
+            onMediaSessionChangeInMediaButtonSessionApp();
+        }
         clearCache();
     }
 
@@ -122,32 +130,33 @@
     }
 
     /**
-     * Notify the priority tracker that a session's state changed.
+     * Notify the priority tracker that a session's playback state changed.
      *
      * @param record The record that changed.
      * @param oldState Its old playback state.
      * @param newState Its new playback state.
-     * @return true if the priority order was updated, false otherwise.
      */
-    public boolean onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
+    public void onPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
         if (shouldUpdatePriority(oldState, newState)) {
             mSessions.remove(record);
             mSessions.add(0, record);
             clearCache();
-            // This becomes the last interesting record since it entered a
-            // playing state
-            mLastInterestingRecord = record;
-            return true;
         } else if (!MediaSession.isActiveState(newState)) {
             // Just clear the volume cache when a state goes inactive
             mCachedVolumeDefault = null;
         }
-        return false;
+
+        // In most cases, playback state isn't needed for finding media buttion session,
+        // but we only use it as a hint if an app has multiple local media sessions.
+        // In that case, we pick the media session whose PlaybackState matches
+        // the audio playback configuration.
+        if (mMediaButtonSession != null && mMediaButtonSession.getUid() == record.getUid()) {
+            onMediaSessionChangeInMediaButtonSessionApp();
+        }
     }
 
     /**
-     * Handle any stack changes that need to occur in response to a session
-     * state change. TODO add the old and new session state as params
+     * Handle the change in activeness for a session.
      *
      * @param record The record that changed.
      */
@@ -158,6 +167,81 @@
     }
 
     /**
+     * Update the media button session if needed.
+     * <p>The media button session is the session that will receive the media button events.
+     * <p>We send the media button events to the lastly played app. If the app has the media
+     * session, the session will receive the media button events.
+     */
+    public void updateMediaButtonSessionIfNeeded() {
+        if (DEBUG) {
+            Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
+        }
+        IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+        for (int i = 0; i < audioPlaybackUids.size(); i++) {
+            MediaSessionRecord mediaButtonSession =
+                    findMediaButtonSession(audioPlaybackUids.get(i));
+            if (mediaButtonSession != null) {
+                // Found the media button session.
+                mAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
+                if (mMediaButtonSession != mediaButtonSession) {
+                    mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(
+                            mMediaButtonSession, mediaButtonSession);
+                    mMediaButtonSession = mediaButtonSession;
+                }
+                return;
+            }
+        }
+    }
+
+    /**
+     * Handle the change in a media session in the media button session app.
+     * <p>If the app has multiple media sessions, change in a media sesion in the app may change
+     * the media button session.
+     * @see #findMediaButtonSession
+     */
+    private void onMediaSessionChangeInMediaButtonSessionApp() {
+        MediaSessionRecord newMediaButtonSession =
+                findMediaButtonSession(mMediaButtonSession.getUid());
+        if (newMediaButtonSession != mMediaButtonSession) {
+            mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(mMediaButtonSession,
+                    newMediaButtonSession);
+            mMediaButtonSession = newMediaButtonSession;
+        }
+    }
+
+    /**
+     * Find the media button session with the given {@param uid}.
+     * If the app has multiple media sessions, the media session matches the audio playback state
+     * becomes the media button session.
+     *
+     * @return The media button session. Returns {@code null} if the app doesn't have a media
+     *   session.
+     */
+    private MediaSessionRecord findMediaButtonSession(int uid) {
+        MediaSessionRecord mediaButtonSession = null;
+        for (MediaSessionRecord session : mSessions) {
+            // Since the media buttons come with the headset/speaker, users will expect changes in
+            // the headset/speaker when they press media buttons. So only consider local playback
+            // for the media button session.
+            if (uid == session.getUid()
+                    && session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+                if (session.isPlaybackActive() ==
+                        mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
+                    // If there's a media session whose PlaybackState matches
+                    // the audio playback state, return it immediately.
+                    return session;
+                }
+                if (mediaButtonSession == null) {
+                    // Among the media sessions whose PlaybackState doesn't match
+                    // the audio playback state, pick the top priority.
+                    mediaButtonSession = session;
+                }
+            }
+        }
+        return mediaButtonSession;
+    }
+
+    /**
      * Get the current priority sorted list of active sessions. The most
      * important session is at index 0 and the least important at size - 1.
      *
@@ -166,57 +250,29 @@
      */
     public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
         if (mCachedActiveList == null) {
-            mCachedActiveList = getPriorityList(true, 0, userId);
+            mCachedActiveList = getPriorityList(true, userId);
         }
         return mCachedActiveList;
     }
 
     /**
-     * Get the highest priority session that can handle media buttons.
+     * Get the media button session which receives the media button events.
      *
-     * @param includeNotPlaying Return a non-playing session if nothing else is
-     *            available
-     * @return The default media button session or null.
+     * @return The media button session or null.
      */
-    public MediaSessionRecord getDefaultMediaButtonSession(boolean includeNotPlaying) {
-        if (mCachedButtonReceiver != null) {
-            return mCachedButtonReceiver;
-        }
-        ArrayList<MediaSessionRecord> records = getPriorityList(true,
-                MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, UserHandle.USER_ALL);
-        if (records.size() > 0) {
-            MediaSessionRecord record = records.get(0);
-            if (record.isPlaybackActive(false)) {
-                // Since we're going to send a button event to this record make
-                // it the last interesting one.
-                mLastInterestingRecord = record;
-                mCachedButtonReceiver = record;
-            } else if (mLastInterestingRecord != null) {
-                if (records.contains(mLastInterestingRecord)) {
-                    mCachedButtonReceiver = mLastInterestingRecord;
-                } else {
-                    // That record is no longer used. Clear its reference.
-                    mLastInterestingRecord = null;
-                }
-            }
-            if (includeNotPlaying && mCachedButtonReceiver == null) {
-                // If we really want a record and we didn't find one yet use the
-                // highest priority session even if it's not playing.
-                mCachedButtonReceiver = record;
-            }
-        }
-        return mCachedButtonReceiver;
+    public MediaSessionRecord getMediaButtonSession() {
+        return mMediaButtonSession;
     }
 
     public MediaSessionRecord getDefaultVolumeSession() {
         if (mCachedVolumeDefault != null) {
             return mCachedVolumeDefault;
         }
-        ArrayList<MediaSessionRecord> records = getPriorityList(true, 0, UserHandle.USER_ALL);
+        ArrayList<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
         int size = records.size();
         for (int i = 0; i < size; i++) {
             MediaSessionRecord record = records.get(i);
-            if (record.isPlaybackActive(false)) {
+            if (record.isPlaybackActive()) {
                 mCachedVolumeDefault = record;
                 return record;
             }
@@ -225,7 +281,7 @@
     }
 
     public MediaSessionRecord getDefaultRemoteSession(int userId) {
-        ArrayList<MediaSessionRecord> records = getPriorityList(true, 0, userId);
+        ArrayList<MediaSessionRecord> records = getPriorityList(true, userId);
 
         int size = records.size();
         for (int i = 0; i < size; i++) {
@@ -238,9 +294,10 @@
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false, 0,
+        ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false,
                 UserHandle.USER_ALL);
         int count = sortedSessions.size();
+        pw.println(prefix + "Media button session is " + mMediaButtonSession);
         pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
         String indent = prefix + "  ";
         for (int i = 0; i < count; i++) {
@@ -252,22 +309,23 @@
 
     /**
      * Get a priority sorted list of sessions. Can filter to only return active
-     * sessions or sessions with specific flags.
+     * sessions or sessions.
+     * <p>Here's the priority order.
+     * <li>System priority session (session with FLAG_EXCLUSIVE_GLOBAL_PRIORITY)</li>
+     * <li>Active sessions whose PlaybackState is active</li>
+     * <li>Active sessions whose PlaybackState is inactive</li>
+     * <li>Inactive sessions</li>
      *
      * @param activeOnly True to only return active sessions, false to return
      *            all sessions.
-     * @param withFlags Only return sessions with all the specified flags set. 0
-     *            returns all sessions.
      * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
      *            will return sessions for all users.
      * @return The priority sorted list of sessions.
      */
-    public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int withFlags,
-            int userId) {
+    public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
         ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
-        int lastLocalIndex = 0;
+        int lastPlaybackActiveIndex = 0;
         int lastActiveIndex = 0;
-        int lastPublishedIndex = 0;
 
         int size = mSessions.size();
         for (int i = 0; i < size; i++) {
@@ -277,10 +335,7 @@
                 // Filter out sessions for the wrong user
                 continue;
             }
-            if ((session.getFlags() & withFlags) != withFlags) {
-                // Filter out sessions with the wrong flags
-                continue;
-            }
+
             if (!session.isActive()) {
                 if (!activeOnly) {
                     // If we're getting unpublished as well always put them at
@@ -294,28 +349,13 @@
                 // System priority sessions are special and always go at the
                 // front. We expect there to only be one of these at a time.
                 result.add(0, session);
-                lastLocalIndex++;
+                lastPlaybackActiveIndex++;
                 lastActiveIndex++;
-                lastPublishedIndex++;
-            } else if (session.isPlaybackActive(true)) {
-                // TODO this with real local route check
-                if (true) {
-                    // Active local sessions get top priority
-                    result.add(lastLocalIndex, session);
-                    lastLocalIndex++;
-                    lastActiveIndex++;
-                    lastPublishedIndex++;
-                } else {
-                    // Then active remote sessions
-                    result.add(lastActiveIndex, session);
-                    lastActiveIndex++;
-                    lastPublishedIndex++;
-                }
+            } else if (session.isPlaybackActive()) {
+                result.add(lastPlaybackActiveIndex++, session);
+                lastActiveIndex++;
             } else {
-                // inactive sessions go at the end in order of whoever last did
-                // something.
-                result.add(lastPublishedIndex, session);
-                lastPublishedIndex++;
+                result.add(lastActiveIndex++, session);
             }
         }
 
@@ -345,8 +385,6 @@
     private void clearCache() {
         mCachedDefault = null;
         mCachedVolumeDefault = null;
-        mCachedButtonReceiver = null;
         mCachedActiveList = null;
-        mCachedTransportControlList = null;
     }
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 8a4f3f7..fc45344 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -322,6 +322,12 @@
     private INotificationManager mNotifManager;
     private PowerManagerInternal mPowerManagerInternal;
     private IDeviceIdleController mDeviceIdleController;
+    @GuardedBy("mUidRulesFirstLock")
+    private PowerSaveState mRestrictBackgroundPowerState;
+
+    // Store the status of restrict background before turning on battery saver.
+    // Used to restore mRestrictBackground when battery saver is turned off.
+    private boolean mRestrictBackgroundBeforeBsm;
 
     // See main javadoc for instructions on how to use these locks.
     final Object mUidRulesFirstLock = new Object();
@@ -332,6 +338,8 @@
     @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictBackground;
     @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictPower;
     @GuardedBy("mUidRulesFirstLock") volatile boolean mDeviceIdleMode;
+    // Store whether user flipped restrict background in battery saver mode
+    @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictBackgroundChangedInBsm;
 
     private final boolean mSuppressDefaultPolicy;
 
@@ -617,8 +625,9 @@
                                 @Override
                                 public void onLowPowerModeChanged(PowerSaveState result) {
                                     final boolean enabled = result.batterySaverEnabled;
-                                    if (LOGD) Slog.d(TAG,
-                                            "onLowPowerModeChanged(" + enabled + ")");
+                                    if (LOGD) {
+                                        Slog.d(TAG, "onLowPowerModeChanged(" + enabled + ")");
+                                    }
                                     synchronized (mUidRulesFirstLock) {
                                         if (mRestrictPower != enabled) {
                                             mRestrictPower = enabled;
@@ -626,7 +635,7 @@
                                         }
                                     }
                                 }
-                    });
+                            });
                     mRestrictPower = mPowerManagerInternal.getLowPowerState(
                             ServiceType.NETWORK_FIREWALL).batterySaverEnabled;
 
@@ -635,6 +644,32 @@
                     // read policy from disk
                     readPolicyAL();
 
+                    // Update the restrictBackground if battery saver is turned on
+                    mRestrictBackgroundBeforeBsm = mRestrictBackground;
+                    mRestrictBackgroundPowerState = mPowerManagerInternal
+                            .getLowPowerState(ServiceType.DATA_SAVER);
+                    final boolean localRestrictBackground =
+                            mRestrictBackgroundPowerState.batterySaverEnabled;
+                    if (localRestrictBackground && localRestrictBackground != mRestrictBackground) {
+                        mRestrictBackground = localRestrictBackground;
+                        mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_CHANGED,
+                                mRestrictBackground ? 1 : 0, 0).sendToTarget();
+                    }
+                    mPowerManagerInternal.registerLowPowerModeObserver(
+                            new PowerManagerInternal.LowPowerModeListener() {
+                                @Override
+                                public int getServiceType() {
+                                    return ServiceType.DATA_SAVER;
+                                }
+
+                                @Override
+                                public void onLowPowerModeChanged(PowerSaveState result) {
+                                    synchronized (mUidRulesFirstLock) {
+                                        updateRestrictBackgroundByLowPowerModeUL(result);
+                                    }
+                                }
+                            });
+
                     if (addDefaultRestrictBackgroundWhitelistUidsUL()) {
                         writePolicyAL();
                     }
@@ -2159,6 +2194,10 @@
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
+
+        if (mRestrictBackgroundPowerState.globalBatterySaverEnabled) {
+            mRestrictBackgroundChangedInBsm = true;
+        }
         synchronized (mNetworkPoliciesSecondLock) {
             updateNotificationsNL();
             writePolicyAL();
@@ -3645,6 +3684,35 @@
         mHandler.getLooper().getQueue().addIdleHandler(handler);
     }
 
+    @VisibleForTesting
+    public void updateRestrictBackgroundByLowPowerModeUL(final PowerSaveState result) {
+        mRestrictBackgroundPowerState = result;
+
+        boolean restrictBackground = result.batterySaverEnabled;
+        boolean shouldInvokeRestrictBackground;
+        // store the temporary mRestrictBackgroundChangedInBsm and update it at last
+        boolean localRestrictBgChangedInBsm = mRestrictBackgroundChangedInBsm;
+
+        if (result.globalBatterySaverEnabled) {
+            // Try to turn on restrictBackground if (1) it is off and (2) batter saver need to
+            // turn it on.
+            shouldInvokeRestrictBackground = !mRestrictBackground && result.batterySaverEnabled;
+            mRestrictBackgroundBeforeBsm = mRestrictBackground;
+            localRestrictBgChangedInBsm = false;
+        } else {
+            // Try to restore the restrictBackground if it doesn't change in bsm
+            shouldInvokeRestrictBackground = !mRestrictBackgroundChangedInBsm;
+            restrictBackground = mRestrictBackgroundBeforeBsm;
+        }
+
+        if (shouldInvokeRestrictBackground) {
+            setRestrictBackground(restrictBackground);
+        }
+
+        // Change it at last so setRestrictBackground() won't affect this variable
+        mRestrictBackgroundChangedInBsm = localRestrictBgChangedInBsm;
+    }
+
     private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
         final int size = source.size();
         for (int i = 0; i < size; i++) {
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/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 8d20531..1781d8c 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -43,7 +43,8 @@
             ServiceType.NETWORK_FIREWALL,
             ServiceType.SCREEN_BRIGHTNESS,
             ServiceType.SOUND,
-            ServiceType.BATTERY_STATS})
+            ServiceType.BATTERY_STATS,
+            ServiceType.DATA_SAVER})
     public @interface ServiceType {
         int NULL = 0;
         int GPS = 1;
@@ -55,6 +56,7 @@
         int SCREEN_BRIGHTNESS = 7;
         int SOUND = 8;
         int BATTERY_STATS = 9;
+        int DATA_SAVER = 10;
     }
 
     private static final String TAG = "BatterySaverPolicy";
@@ -73,6 +75,7 @@
     private static final String KEY_SOUNDTRIGGER_DISABLED = "soundtrigger_disabled";
     private static final String KEY_FIREWALL_DISABLED = "firewall_disabled";
     private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled";
+    private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled";
     private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
     private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
     private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
@@ -137,6 +140,14 @@
     private boolean mAdjustBrightnessDisabled;
 
     /**
+     * {@code true} if data saver is disabled in battery saver mode.
+     *
+     * @see Settings.Global#BATTERY_SAVER_CONSTANTS
+     * @see #KEY_DATASAVER_DISABLED
+     */
+    private boolean mDataSaverDisabled;
+
+    /**
      * This is the flag to decide the gps mode in battery saver mode.
      *
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
@@ -191,6 +202,7 @@
             mFireWallDisabled = mParser.getBoolean(KEY_FIREWALL_DISABLED, false);
             mAdjustBrightnessDisabled = mParser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
             mAdjustBrightnessFactor = mParser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
+            mDataSaverDisabled = mParser.getBoolean(KEY_DATASAVER_DISABLED, true);
 
             // Get default value from Settings.Secure
             final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
@@ -210,7 +222,8 @@
      */
     public PowerSaveState getBatterySaverPolicy(@ServiceType int type, boolean realMode) {
         synchronized (BatterySaverPolicy.this) {
-            final PowerSaveState.Builder builder = new PowerSaveState.Builder();
+            final PowerSaveState.Builder builder = new PowerSaveState.Builder()
+                    .setGlobalBatterySaverEnabled(realMode);
             if (!realMode) {
                 return builder.setBatterySaverEnabled(realMode)
                         .build();
@@ -236,6 +249,9 @@
                     return builder.setBatterySaverEnabled(!mAdjustBrightnessDisabled)
                             .setBrightnessFactor(mAdjustBrightnessFactor)
                             .build();
+                case ServiceType.DATA_SAVER:
+                    return builder.setBatterySaverEnabled(!mDataSaverDisabled)
+                            .build();
                 case ServiceType.SOUND:
                     return builder.setBatterySaverEnabled(mSoundTriggerDisabled)
                             .build();
@@ -262,6 +278,7 @@
         pw.println("  " + KEY_FULLBACKUP_DEFERRED + "=" + mFullBackupDeferred);
         pw.println("  " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
         pw.println("  " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
+        pw.println("  " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
         pw.println("  " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
         pw.println("  " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
         pw.println("  " + KEY_GPS_MODE + "=" + mGpsMode);
diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java
index 5a1f473..904d915 100644
--- a/services/core/java/com/android/server/storage/AppFuseBridge.java
+++ b/services/core/java/com/android/server/storage/AppFuseBridge.java
@@ -19,18 +19,20 @@
 import android.os.ParcelFileDescriptor;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import libcore.io.IoUtils;
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Runnable that delegates FUSE command from the kernel to application.
  * run() blocks until all opened files on the FUSE mount point are closed. So this should be run in
  * a separated thread.
  */
-public class AppFuseBridge implements Runnable, AutoCloseable {
+public class AppFuseBridge implements Runnable {
     public static final String TAG = "AppFuseBridge";
 
     /**
@@ -41,71 +43,138 @@
      */
     private static final String APPFUSE_MOUNT_NAME_TEMPLATE = "/mnt/appfuse/%d_%d";
 
-    private final IMountScope mMountScope;
-    private final ParcelFileDescriptor mProxyFd;
-    private final BlockingQueue<Boolean> mChannel;
+    @GuardedBy("this")
+    private final SparseArray<MountScope> mScopes = new SparseArray<>();
 
-    /**
-     * @param mountScope Listener to unmount mount point.
-     * @param proxyFd FD of socket pair. Ownership of FD is taken by AppFuseBridge.
-     * @param channel Channel that the runnable send mount result to.
-     */
-    public AppFuseBridge(
-            IMountScope mountScope, ParcelFileDescriptor proxyFd, BlockingQueue<Boolean> channel) {
-        Preconditions.checkNotNull(mountScope);
-        Preconditions.checkNotNull(proxyFd);
-        Preconditions.checkNotNull(channel);
-        mMountScope = mountScope;
-        mProxyFd = proxyFd;
-        mChannel = channel;
+    @GuardedBy("this")
+    private long mNativeLoop;
+
+    public AppFuseBridge() {
+        mNativeLoop = native_new();
+    }
+
+    public ParcelFileDescriptor addBridge(MountScope mountScope)
+            throws BridgeException {
+        try {
+            synchronized (this) {
+                Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0);
+                if (mNativeLoop == 0) {
+                    throw new BridgeException("The thread has already been terminated");
+                }
+                final int fd = native_add_bridge(
+                        mNativeLoop, mountScope.mountId, mountScope.deviceFd.detachFd());
+                if (fd == -1) {
+                    throw new BridgeException("Failed to invoke native_add_bridge");
+                }
+                final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd);
+                mScopes.put(mountScope.mountId, mountScope);
+                mountScope = null;
+                return result;
+            }
+        } finally {
+            IoUtils.closeQuietly(mountScope);
+        }
     }
 
     @Override
     public void run() {
-        try {
-            // deviceFd and proxyFd must be closed in native_start_loop.
-            native_start_loop(
-                    mMountScope.getDeviceFileDescriptor().detachFd(),
-                    mProxyFd.detachFd());
-        } finally {
-            close();
+        native_start_loop(mNativeLoop);
+        synchronized (this) {
+            native_delete(mNativeLoop);
+            mNativeLoop = 0;
         }
     }
 
-    public static ParcelFileDescriptor openFile(int uid, int mountId, int fileId, int mode)
-            throws FileNotFoundException {
-        final File mountPoint = getMountPoint(uid, mountId);
+    public ParcelFileDescriptor openFile(int pid, int mountId, int fileId, int mode)
+            throws FileNotFoundException, SecurityException, InterruptedException {
+        final MountScope scope;
+        synchronized (this) {
+            scope = mScopes.get(mountId);
+            if (scope == null) {
+                throw new FileNotFoundException("Cannot find mount point");
+            }
+        }
+        if (scope.pid != pid) {
+            throw new SecurityException("PID does not match");
+        }
+        final boolean result = scope.waitForMount();
+        if (result == false) {
+            throw new FileNotFoundException("Mount failed");
+        }
         try {
-            if (Os.stat(mountPoint.getPath()).st_ino != 1) {
+            if (Os.stat(scope.mountPoint.getPath()).st_ino != 1) {
                 throw new FileNotFoundException("Could not find bridge mount point.");
             }
         } catch (ErrnoException e) {
             throw new FileNotFoundException(
-                    "Failed to stat mount point: " + mountPoint.getParent());
+                    "Failed to stat mount point: " + scope.mountPoint.getParent());
         }
-        return ParcelFileDescriptor.open(new File(mountPoint, String.valueOf(fileId)), mode);
-    }
-
-    private static File getMountPoint(int uid, int mountId) {
-        return new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE,  uid, mountId));
-    }
-
-    @Override
-    public void close() {
-        IoUtils.closeQuietly(mMountScope);
-        IoUtils.closeQuietly(mProxyFd);
-        // Invoke countDown here in case where close is invoked before mount.
-        mChannel.offer(false);
+        return ParcelFileDescriptor.open(new File(scope.mountPoint, String.valueOf(fileId)), mode);
     }
 
     // Used by com_android_server_storage_AppFuse.cpp.
-    private void onMount() {
-        mChannel.offer(true);
+    synchronized private void onMount(int mountId) {
+        final MountScope scope = mScopes.get(mountId);
+        if (scope != null) {
+            scope.setMountResultLocked(true);
+        }
     }
 
-    public static interface IMountScope extends AutoCloseable {
-        ParcelFileDescriptor getDeviceFileDescriptor();
+    // Used by com_android_server_storage_AppFuse.cpp.
+    synchronized private void onClosed(int mountId) {
+        final MountScope scope = mScopes.get(mountId);
+        if (scope != null) {
+            scope.setMountResultLocked(false);
+            IoUtils.closeQuietly(scope);
+            mScopes.remove(mountId);
+        }
     }
 
-    private native boolean native_start_loop(int deviceFd, int proxyFd);
+    public static class MountScope implements AutoCloseable {
+        public final int uid;
+        public final int pid;
+        public final int mountId;
+        public final ParcelFileDescriptor deviceFd;
+        public final File mountPoint;
+        private final CountDownLatch mMounted = new CountDownLatch(1);
+        private boolean mMountResult = false;
+
+        public MountScope(int uid, int pid, int mountId, ParcelFileDescriptor deviceFd) {
+            this.uid = uid;
+            this.pid = pid;
+            this.mountId = mountId;
+            this.deviceFd = deviceFd;
+            this.mountPoint = new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE,  uid, mountId));
+        }
+
+        @GuardedBy("AppFuseBridge.this")
+        void setMountResultLocked(boolean result) {
+            if (mMounted.getCount() == 0) {
+                return;
+            }
+            mMountResult = result;
+            mMounted.countDown();
+        }
+
+        boolean waitForMount() throws InterruptedException {
+            mMounted.await();
+            return mMountResult;
+        }
+
+        @Override
+        public void close() throws Exception {
+            deviceFd.close();
+        }
+    }
+
+    public static class BridgeException extends Exception {
+        public BridgeException(String message) {
+            super(message);
+        }
+    }
+
+    private native long native_new();
+    private native void native_delete(long loop);
+    private native void native_start_loop(long loop);
+    private native int native_add_bridge(long loop, int mountId, int deviceId);
 }
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 41c5331..82dd9ac 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -197,7 +197,7 @@
                                     .setQuota(CacheQuotaHint.QUOTA_NOT_SET)
                                     .build());
                 } catch (PackageManager.NameNotFoundException e) {
-                    Slog.w(TAG, "Unable to find package for quota calculation", e);
+                    // This may happen if an app has a recorded usage, but has been uninstalled.
                     continue;
                 }
             }
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/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index e1df0ba..731f53f 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -37,6 +37,7 @@
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
@@ -44,6 +45,7 @@
 import android.service.vr.IVrListener;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
+import android.service.vr.IVrWindowManager;
 import android.service.vr.VrListenerService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -427,6 +429,18 @@
         }
 
         @Override
+        public void connectController(FileDescriptor fd) throws android.os.RemoteException {
+            enforceCallerPermission(Manifest.permission.RESTRICTED_VR_ACCESS);
+            VrManagerService.this.connectController(fd);
+        }
+
+        @Override
+        public void disconnectController() throws android.os.RemoteException {
+            enforceCallerPermission(Manifest.permission.RESTRICTED_VR_ACCESS);
+            VrManagerService.this.disconnectController();
+        }
+
+        @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                     != PackageManager.PERMISSION_GRANTED) {
@@ -1151,4 +1165,20 @@
             return mVrModeEnabled;
         }
     }
+
+    private void connectController(FileDescriptor fd) throws android.os.RemoteException {
+        // TODO(b/36506799): move vr_wm code to VrCore and remove this.
+        IVrWindowManager remote =
+                IVrWindowManager.Stub.asInterface(
+                        ServiceManager.getService(IVrWindowManager.SERVICE_NAME));
+        remote.connectController(fd);
+    }
+
+    private void disconnectController() throws android.os.RemoteException {
+        // TODO(b/36506799): move vr_wm code to VrCore and remove this.
+        IVrWindowManager remote =
+                IVrWindowManager.Stub.asInterface(
+                        ServiceManager.getService(IVrWindowManager.SERVICE_NAME));
+        remote.disconnectController();
+    }
 }
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..1decf4e 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.StackId;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -164,6 +165,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 +179,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 +197,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 +205,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) {
@@ -1175,12 +1205,23 @@
      * in anyway.
      */
     @Override
-    int getOrientation() {
+    int getOrientation(int candidate) {
+        if (!fillsParent()) {
+            // Can't specify orientation if app doesn't fill parent.
+            return SCREEN_ORIENTATION_UNSET;
+        }
+
+        if (candidate == SCREEN_ORIENTATION_BEHIND) {
+            // Allow app to specify orientation regardless of its visibility state if the current
+            // candidate want us to use orientation behind. I.e. the visible app on-top of this one
+            // wants us to use the orientation of the app behind it.
+            return mOrientation;
+        }
+
         // The {@link AppWindowToken} should only specify an orientation when it is not closing or
         // going to the bottom. Allowing closing {@link AppWindowToken} to participate can lead to
         // an Activity in another task being started in the wrong orientation during the transition.
-        if (fillsParent()
-                && !(sendingToBottom || mService.mClosingApps.contains(this))
+        if (!(sendingToBottom || mService.mClosingApps.contains(this))
                 && (isVisible() || mService.mOpeningApps.contains(this))) {
             return mOrientation;
         }
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/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index fc37797..85eae02 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -540,12 +540,14 @@
         checkMinimizeChanged(true /* animate */);
 
         // We were minimized, and now we are still minimized, but somebody is trying to launch an
-        // app in docked stack, better show recent apps so we actually get unminimized! This catches
+        // app in docked stack, better show recent apps so we actually get unminimized! However do
+        // not do this if keyguard is dismissed such as when the device is unlocking. This catches
         // any case that was missed in ActivityStarter.postStartActivityUncheckedProcessing because
         // we couldn't retrace the launch of the app in the docked stack to the launch from
         // homescreen.
         if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps)
-                && appTransition != TRANSIT_NONE) {
+                && appTransition != TRANSIT_NONE &&
+                !AppTransition.isKeyguardGoingAwayTransit(appTransition)) {
             mService.showRecentApps(true /* fromHome */);
         }
     }
@@ -579,6 +581,12 @@
         if (homeTask == null || !isWithinDisplay(homeTask)) {
             return;
         }
+
+        // Do not minimize when dock is already minimized while keyguard is showing and not
+        // occluded such as unlocking the screen
+        if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
+            return;
+        }
         final TaskStack fullscreenStack =
                 mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
         final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 012480e..3ce61f0 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -195,7 +195,8 @@
      * @return whether the given {@param aspectRatio} is valid.
      */
     public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
-        return mMinAspectRatio <= aspectRatio && aspectRatio <= mMaxAspectRatio;
+        return Float.compare(mMinAspectRatio, aspectRatio) <= 0 &&
+                Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 68d0f24..b0e3e32 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -152,8 +152,7 @@
     }
 
     WindowState computeFocusedWindow() {
-        final int count = mChildren.size();
-        for (int i = 0; i < count; i++) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
             final DisplayContent dc = mChildren.get(i);
             final WindowState win = dc.findFocusedWindow();
             if (win != null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6973c3c..84ba139 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();
+        }
     }
 
     /**
@@ -502,14 +516,22 @@
         mOrientation = orientation;
     }
 
+    int getOrientation() {
+        return getOrientation(mOrientation);
+    }
+
     /**
      * Returns the specified orientation for this window container or one of its children is there
      * is one set, or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSET} if no
      * specification is set.
      * NOTE: {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} is a
      * specification...
+     *
+     * @param candidate The current orientation candidate that will be returned if we don't find a
+     *                  better match.
+     * @return The orientation as specified by this branch or the window hierarchy.
      */
-    int getOrientation() {
+    int getOrientation(int candidate) {
         if (!fillsParent()) {
             // Ignore containers that don't completely fill their parents.
             return SCREEN_ORIENTATION_UNSET;
@@ -523,12 +545,14 @@
                 && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
             return mOrientation;
         }
-        int candidate = mOrientation;
 
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
 
-            final int orientation = wc.getOrientation();
+            // TODO: Maybe mOrientation should default to SCREEN_ORIENTATION_UNSET vs.
+            // SCREEN_ORIENTATION_UNSPECIFIED?
+            final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND
+                    ? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET);
             if (orientation == SCREEN_ORIENTATION_BEHIND) {
                 // container wants us to use the orientation of the container behind it. See if we
                 // can find one. Else return SCREEN_ORIENTATION_BEHIND so the caller can choose to
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/core/jni/com_android_server_storage_AppFuseBridge.cpp b/services/core/jni/com_android_server_storage_AppFuseBridge.cpp
index 2f20ecd..c8f842d 100644
--- a/services/core/jni/com_android_server_storage_AppFuseBridge.cpp
+++ b/services/core/jni/com_android_server_storage_AppFuseBridge.cpp
@@ -19,16 +19,19 @@
 
 #include <android_runtime/Log.h>
 #include <android-base/logging.h>
+#include <android-base/unique_fd.h>
 #include <core_jni_helpers.h>
 #include <libappfuse/FuseBridgeLoop.h>
+#include <libappfuse/FuseBuffer.h>
 #include <nativehelper/JNIHelp.h>
 
 namespace android {
 namespace {
 
 constexpr const char* CLASS_NAME = "com/android/server/storage/AppFuseBridge";
-static jclass appFuseClass;
-static jmethodID appFuseOnMount;
+static jclass gAppFuseClass;
+static jmethodID gAppFuseOnMount;
+static jmethodID gAppFuseOnClosed;
 
 class Callback : public fuse::FuseBridgeLoopCallback {
     JNIEnv* mEnv;
@@ -36,8 +39,16 @@
 
 public:
     Callback(JNIEnv* env, jobject self) : mEnv(env), mSelf(self) {}
-    void OnMount() override {
-        mEnv->CallVoidMethod(mSelf, appFuseOnMount);
+    void OnMount(int mount_id) override {
+        mEnv->CallVoidMethod(mSelf, gAppFuseOnMount, mount_id);
+        if (mEnv->ExceptionCheck()) {
+            LOGE_EX(mEnv, nullptr);
+            mEnv->ExceptionClear();
+        }
+    }
+
+    void OnClosed(int mount_id) override {
+        mEnv->CallVoidMethod(mSelf, gAppFuseOnClosed, mount_id);
         if (mEnv->ExceptionCheck()) {
             LOGE_EX(mEnv, nullptr);
             mEnv->ExceptionClear();
@@ -45,17 +56,93 @@
     }
 };
 
-jboolean com_android_server_storage_AppFuseBridge_start_loop(
-        JNIEnv* env, jobject self, jint devJavaFd, jint proxyJavaFd) {
+class MonitorScope final {
+public:
+    MonitorScope(JNIEnv* env, jobject obj) : mEnv(env), mObj(obj), mLocked(false) {
+        if (mEnv->MonitorEnter(obj) == JNI_OK) {
+            mLocked = true;
+        } else {
+            LOG(ERROR) << "Failed to enter monitor.";
+        }
+    }
+
+    ~MonitorScope() {
+        if (mLocked) {
+            if (mEnv->MonitorExit(mObj) != JNI_OK) {
+                LOG(ERROR) << "Failed to exit monitor.";
+            }
+        }
+    }
+
+    operator bool() {
+        return mLocked;
+    }
+
+private:
+    // Lifetime of |MonitorScope| must be shorter than the reference of mObj.
+    JNIEnv* mEnv;
+    jobject mObj;
+    bool mLocked;
+
+    DISALLOW_COPY_AND_ASSIGN(MonitorScope);
+};
+
+jlong com_android_server_storage_AppFuseBridge_new(JNIEnv* env, jobject self) {
+    return reinterpret_cast<jlong>(new fuse::FuseBridgeLoop());
+}
+
+void com_android_server_storage_AppFuseBridge_delete(JNIEnv* env, jobject self, jlong java_loop) {
+    fuse::FuseBridgeLoop* const loop = reinterpret_cast<fuse::FuseBridgeLoop*>(java_loop);
+    CHECK(loop);
+    delete loop;
+}
+
+void com_android_server_storage_AppFuseBridge_start_loop(
+        JNIEnv* env, jobject self, jlong java_loop) {
+    fuse::FuseBridgeLoop* const loop = reinterpret_cast<fuse::FuseBridgeLoop*>(java_loop);
+    CHECK(loop);
     Callback callback(env, self);
-    return fuse::StartFuseBridgeLoop(devJavaFd, proxyJavaFd, &callback);
+    loop->Start(&callback);
+}
+
+jint com_android_server_storage_AppFuseBridge_add_bridge(
+        JNIEnv* env, jobject self, jlong java_loop, jint mountId, jint javaDevFd) {
+    base::unique_fd devFd(javaDevFd);
+    fuse::FuseBridgeLoop* const loop = reinterpret_cast<fuse::FuseBridgeLoop*>(java_loop);
+    CHECK(loop);
+
+    base::unique_fd proxyFd[2];
+    if (!fuse::SetupMessageSockets(&proxyFd)) {
+        return -1;
+    }
+
+    if (!loop->AddBridge(mountId, std::move(devFd), std::move(proxyFd[0]))) {
+        return -1;
+    }
+
+    return proxyFd[1].release();
 }
 
 const JNINativeMethod methods[] = {
     {
+        "native_new",
+        "()J",
+        reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_new)
+    },
+    {
+        "native_delete",
+        "(J)V",
+        reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_delete)
+    },
+    {
         "native_start_loop",
-        "(II)Z",
-        (void *) com_android_server_storage_AppFuseBridge_start_loop
+        "(J)V",
+        reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_start_loop)
+    },
+    {
+        "native_add_bridge",
+        "(JII)I",
+        reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_add_bridge)
     }
 };
 
@@ -64,8 +151,9 @@
 void register_android_server_storage_AppFuse(JNIEnv* env) {
     CHECK(env != nullptr);
 
-    appFuseClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
-    appFuseOnMount = GetMethodIDOrDie(env, appFuseClass, "onMount", "()V");
+    gAppFuseClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
+    gAppFuseOnMount = GetMethodIDOrDie(env, gAppFuseClass, "onMount", "(I)V");
+    gAppFuseOnClosed = GetMethodIDOrDie(env, gAppFuseClass, "onClosed", "(I)V");
     RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods));
 }
 }  // namespace android
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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f0732dd..da49eb3 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -317,7 +317,7 @@
 
             // In case the runtime switched since last boot (such as when
             // the old runtime was removed in an OTA), set the system
-            // property so that it is in sync. We can't do this in
+            // property so that it is in sync. We can | xq oqi't do this in
             // libnativehelper's JniInvocation::Init code where we already
             // had to fallback to a different runtime because it is
             // running as root and we need to be the system user to set
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/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index 9356dac..7790698 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -20,6 +20,7 @@
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.Manifest;
+import android.annotation.CheckResult;
 import android.annotation.Nullable;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -36,8 +37,11 @@
 import android.net.NetworkPolicyManager;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
+import android.os.IInterface;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -68,11 +72,13 @@
 import java.util.function.Function;
 
 //TODO move to own package!
-//TODO un/linkToDeath & onBinderDied - unbind
 //TODO onStop schedule unbind in 5 seconds
-//TODO Prune association on app uninstall
+//TODO make sure APIs are only callable from currently focused app
+//TODO schedule stopScan on activity destroy(except if configuration change)
+//TODO on associate called again after configuration change -> replace old callback with new
+//TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example)
 /** @hide */
-public class CompanionDeviceManagerService extends SystemService {
+public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
 
     private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
             CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
@@ -90,6 +96,8 @@
     private final CompanionDeviceManagerImpl mImpl;
     private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
     private IDeviceIdleController mIdleController;
+    private IFindDeviceCallback mFindDeviceCallback;
+    private ServiceConnection mServiceConnection;
 
     public CompanionDeviceManagerService(Context context) {
         super(context);
@@ -125,7 +133,51 @@
         publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
     }
 
+    @Override
+    public void binderDied() {
+        Handler.getMain().post(this::handleBinderDied);
+    }
+
+    private void handleBinderDied() {
+        mServiceConnection = unbind(mServiceConnection);
+        mFindDeviceCallback = unlinkToDeath(mFindDeviceCallback, this, 0);
+    }
+
+    /**
+     * Usage: {@code a = unlinkToDeath(a, deathRecipient, flags); }
+     */
+    @Nullable
+    @CheckResult
+    private static <T extends IInterface> T unlinkToDeath(T iinterface,
+            IBinder.DeathRecipient deathRecipient, int flags) {
+        if (iinterface != null) {
+            iinterface.asBinder().unlinkToDeath(deathRecipient, flags);
+        }
+        return null;
+    }
+
+    @Nullable
+    @CheckResult
+    private ServiceConnection unbind(@Nullable ServiceConnection conn) {
+        if (conn != null) {
+            getContext().unbindService(conn);
+        }
+        return null;
+    }
+
     class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+
+        @Override
+        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                throws RemoteException {
+            try {
+                return super.onTransact(code, data, reply, flags);
+            } catch (Throwable e) {
+                Slog.e(LOG_TAG, "Error during IPC", e);
+                throw ExceptionUtils.propagate(e, RemoteException.class);
+            }
+        }
+
         @Override
         public void associate(
                 AssociationRequest request,
@@ -135,14 +187,14 @@
                 Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
                         + ", callingPackage = " + callingPackage + ")");
             }
-            checkNotNull(request);
-            checkNotNull(callback);
+            checkNotNull(request, "Request cannot be null");
+            checkNotNull(callback, "Callback cannot be null");
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 //TODO bindServiceAsUser
                 getContext().bindService(
                         new Intent().setComponent(SERVICE_TO_BIND_TO),
-                        getServiceConnection(request, callback, callingPackage),
+                        createServiceConnection(request, callback, callingPackage),
                         Context.BIND_AUTO_CREATE);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
@@ -168,11 +220,11 @@
         return UserHandle.getUserId(Binder.getCallingUid());
     }
 
-    private ServiceConnection getServiceConnection(
+    private ServiceConnection createServiceConnection(
             final AssociationRequest request,
             final IFindDeviceCallback findDeviceCallback,
             final String callingPackage) {
-        return new ServiceConnection() {
+        mServiceConnection = new ServiceConnection() {
             @Override
             public void onServiceConnected(ComponentName name, IBinder service) {
                 if (DEBUG) {
@@ -180,6 +232,14 @@
                             "onServiceConnected(name = " + name + ", service = "
                                     + service + ")");
                 }
+                mFindDeviceCallback = findDeviceCallback;
+                try {
+                    mFindDeviceCallback.asBinder().linkToDeath(
+                            CompanionDeviceManagerService.this, 0);
+                } catch (RemoteException e) {
+                    handleBinderDied();
+                    return;
+                }
                 try {
                     ICompanionDeviceDiscoveryService.Stub
                             .asInterface(service)
@@ -198,6 +258,7 @@
                 if (DEBUG) Slog.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
             }
         };
+        return mServiceConnection;
     }
 
     private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() {
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/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index f8d105e..29c6f89 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -37,6 +37,7 @@
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -51,10 +52,15 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isA;
 import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -89,6 +95,7 @@
 import android.os.Binder;
 import android.os.INetworkManagementService;
 import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
 import android.os.UserHandle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
@@ -198,6 +205,7 @@
 
     private IUidObserver mUidObserver;
     private INetworkManagementEventObserver mNetworkObserver;
+    private PowerManagerInternal mPowerManagerInternal;
 
     private NetworkPolicyListenerAnswer mPolicyListener;
     private NetworkPolicyManagerService mService;
@@ -227,12 +235,16 @@
 
     @BeforeClass
     public static void registerLocalServices() {
-        addLocalServiceMock(PowerManagerInternal.class);
         addLocalServiceMock(DeviceIdleController.LocalService.class);
         final UsageStatsManagerInternal usageStats =
                 addLocalServiceMock(UsageStatsManagerInternal.class);
         when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{});
         mActivityManagerInternal = addLocalServiceMock(ActivityManagerInternal.class);
+
+        final PowerSaveState state = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(false).build();
+        final PowerManagerInternal pmInternal = addLocalServiceMock(PowerManagerInternal.class);
+        when(pmInternal.getLowPowerState(anyInt())).thenReturn(state);
     }
 
     @Before
@@ -401,6 +413,85 @@
         removeRestrictBackgroundWhitelist(false);
     }
 
+    @Test
+    public void testLowPowerModeObserver_ListenersRegistered()
+            throws Exception {
+        PowerManagerInternal pmInternal = LocalServices.getService(PowerManagerInternal.class);
+
+        verify(pmInternal, atLeast(2)).registerLowPowerModeObserver(any());
+    }
+
+    @Test
+    public void updateRestrictBackgroundByLowPowerMode_RestrictOnBeforeBsm_RestrictOnAfterBsm()
+            throws Exception {
+        setRestrictBackground(true);
+        PowerSaveState stateOn = new PowerSaveState.Builder()
+                .setGlobalBatterySaverEnabled(true)
+                .setBatterySaverEnabled(false)
+                .build();
+        mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
+
+        // RestrictBackground should be on even though battery saver want to turn it off
+        assertThat(mService.getRestrictBackground()).isTrue();
+
+        PowerSaveState stateOff = new PowerSaveState.Builder()
+                .setGlobalBatterySaverEnabled(false)
+                .setBatterySaverEnabled(false)
+                .build();
+        mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
+
+        // RestrictBackground should be on, following its previous state
+        assertThat(mService.getRestrictBackground()).isTrue();
+    }
+
+    @Test
+    public void updateRestrictBackgroundByLowPowerMode_RestrictOffBeforeBsm_RestrictOffAfterBsm()
+            throws Exception {
+        setRestrictBackground(false);
+        PowerSaveState stateOn = new PowerSaveState.Builder()
+                .setGlobalBatterySaverEnabled(true)
+                .setBatterySaverEnabled(true)
+                .build();
+
+        doReturn(true).when(mNetworkManager).setDataSaverModeEnabled(true);
+        mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
+
+        // RestrictBackground should be turned on because of battery saver
+        assertThat(mService.getRestrictBackground()).isTrue();
+
+        PowerSaveState stateOff = new PowerSaveState.Builder()
+                .setGlobalBatterySaverEnabled(false)
+                .setBatterySaverEnabled(false)
+                .build();
+        mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
+
+        // RestrictBackground should be off, following its previous state
+        assertThat(mService.getRestrictBackground()).isFalse();
+    }
+
+    @Test
+    public void updateRestrictBackgroundByLowPowerMode_StatusChangedInBsm_DoNotRestore()
+            throws Exception {
+        setRestrictBackground(true);
+        PowerSaveState stateOn = new PowerSaveState.Builder()
+                .setGlobalBatterySaverEnabled(true)
+                .setBatterySaverEnabled(true)
+                .build();
+        mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
+
+        // RestrictBackground should still be on
+        assertThat(mService.getRestrictBackground()).isTrue();
+
+        // User turns off RestrictBackground manually
+        setRestrictBackground(false);
+        PowerSaveState stateOff = new PowerSaveState.Builder().setBatterySaverEnabled(
+                false).build();
+        mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
+
+        // RestrictBackground should be off because user changes it manually
+        assertThat(mService.getRestrictBackground()).isFalse();
+    }
+
     private void removeRestrictBackgroundWhitelist(boolean expectIntent) throws Exception {
         // Sanity checks.
         assertWhitelistUids(UID_A);
@@ -1231,7 +1322,7 @@
 
     private void setRestrictBackground(boolean flag) throws Exception {
         // Must set expectation, otherwise NMPS will reset value to previous one.
-        when(mNetworkManager.setDataSaverModeEnabled(flag)).thenReturn(true);
+        doReturn(true).when(mNetworkManager).setDataSaverModeEnabled(flag);
         mService.setRestrictBackground(flag);
         // Sanity check.
         assertEquals("restrictBackground not set", flag, mService.getRestrictBackground());
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 374aee1..00f6273 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -94,7 +94,7 @@
  * <p>Run with:<pre>
  * mmma -j40 frameworks/base/services/tests/servicestests
  * adb install -r ${OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
- * adb shell am instrument -w -e class package com.android.server.accounts \
+ * adb shell am instrument -w -e package com.android.server.accounts \
  * com.android.frameworks.servicestests\
  * /android.support.test.runner.AndroidJUnitRunner
  * </pre>
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index 2cb8af4..aa37407 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -385,6 +385,42 @@
     }
 
     @Test
+    public void testFindAllVisibilityValues() {
+        long accId = 10;
+        long accId2 = 11;
+        String packageName1 = "com.example.one";
+        String packageName2 = "com.example.two";
+        Account account = new Account("name", "example.com");
+        Account account2 = new Account("name2", "example2.com");
+        assertNull(mAccountsDb.findAccountVisibility(account, packageName1));
+
+        mAccountsDb.insertDeAccount(account, accId);
+        assertNull(mAccountsDb.findAccountVisibility(account, packageName1));
+        assertNull(mAccountsDb.findAccountVisibility(accId, packageName1));
+        mAccountsDb.insertDeAccount(account2, accId2);
+
+        mAccountsDb.setAccountVisibility(accId, packageName1, 1);
+        mAccountsDb.setAccountVisibility(accId, packageName2, 2);
+        mAccountsDb.setAccountVisibility(accId2, packageName1, 1);
+
+        Map<Account, Map<String, Integer>> vis = mAccountsDb.findAllVisibilityValues();
+        assertEquals(vis.size(), 2);
+        Map<String, Integer> accnt1Visibility = vis.get(account);
+        assertEquals(accnt1Visibility.size(), 2);
+        assertEquals(accnt1Visibility.get(packageName1), Integer.valueOf(1));
+        assertEquals(accnt1Visibility.get(packageName2), Integer.valueOf(2));
+        Map<String, Integer> accnt2Visibility = vis.get(account2);
+        assertEquals(accnt2Visibility.size(), 1);
+        assertEquals(accnt2Visibility.get(packageName1), Integer.valueOf(1));
+
+        mAccountsDb.setAccountVisibility(accId2, packageName2, 3);
+        vis = mAccountsDb.findAllVisibilityValues();
+        accnt2Visibility = vis.get(account2);
+        assertEquals(accnt2Visibility.size(), 2);
+        assertEquals(accnt2Visibility.get(packageName2), Integer.valueOf(3));
+    }
+
+    @Test
     public void testVisibilityCleanupTrigger() {
         long accId = 10;
         String packageName1 = "com.example.one";
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..1e038df 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -51,7 +51,9 @@
 import android.app.AppOpsManager;
 import android.app.IApplicationThread;
 import android.app.IUidObserver;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -116,7 +118,9 @@
         UidRecord.CHANGE_ACTIVE
     };
 
+    @Mock private Context mContext;
     @Mock private AppOpsService mAppOpsService;
+    @Mock private PackageManager mPackageManager;
 
     private TestInjector mInjector;
     private ActivityManagerService mAms;
@@ -132,6 +136,9 @@
         mHandler = new TestHandler(mHandlerThread.getLooper());
         mInjector = new TestInjector();
         mAms = new ActivityManagerService(mInjector);
+        mAms.mWaitForNetworkTimeoutMs = 100;
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
     }
 
     @After
@@ -217,6 +224,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,
@@ -589,10 +607,12 @@
         uidRecord.pendingChange = changeItem;
         uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2;
         verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2);
+    }
 
+    @Test
+    public void testEnqueueUidChangeLocked_nullUidRecord() {
         // Use "null" uidRecord to make sure there is no crash.
-        // TODO: currently it crashes, uncomment after fixing it.
-        // mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+        mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
     }
 
     private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
@@ -758,6 +778,11 @@
         private boolean mRestricted = true;
 
         @Override
+        public Context getContext() {
+            return mContext;
+        }
+
+        @Override
         public AppOpsService getAppOpsService(File file, Handler handler) {
             return mAppOpsService;
         }
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/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
index 7282b3e..69589e7 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
@@ -19,7 +19,9 @@
 import android.os.Handler;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.server.power.BatterySaverPolicy.ServiceType;
+
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -40,6 +42,7 @@
             + "animation_disabled=false,"
             + "soundtrigger_disabled=true,"
             + "firewall_disabled=false,"
+            + "datasaver_disabled=false,"
             + "adjust_brightness_disabled=true,"
             + "adjust_brightness_factor=0.7,"
             + "fullbackup_deferred=true,"
@@ -99,6 +102,18 @@
     }
 
     @SmallTest
+    public void testGetBatterySaverPolicy_PolicyDataSaver_DefaultValueCorrect() {
+        mBatterySaverPolicy.updateConstants("");
+        final PowerSaveState batterySaverStateOn =
+                mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.DATA_SAVER, BATTERY_SAVER_ON);
+        assertThat(batterySaverStateOn.batterySaverEnabled).isFalse();
+
+        final PowerSaveState batterySaverStateOff = mBatterySaverPolicy.getBatterySaverPolicy(
+                ServiceType.DATA_SAVER, BATTERY_SAVER_OFF);
+        assertThat(batterySaverStateOff.batterySaverEnabled).isFalse();
+    }
+
+    @SmallTest
     public void testGetBatterySaverPolicy_PolicyScreenBrightness_DefaultValueCorrect() {
         testServiceDefaultValue(ServiceType.SCREEN_BRIGHTNESS);
 
@@ -137,7 +152,8 @@
         assertThat(networkState.batterySaverEnabled).isTrue();
 
         final PowerSaveState screenState =
-                mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS, BATTERY_SAVER_ON);
+                mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS,
+                        BATTERY_SAVER_ON);
         assertThat(screenState.batterySaverEnabled).isFalse();
         assertThat(screenState.brightnessFactor).isWithin(PRECISION).of(BRIGHTNESS_FACTOR);
 
@@ -149,6 +165,10 @@
                 ServiceType.KEYVALUE_BACKUP, BATTERY_SAVER_ON);
         assertThat(keyValueBackupState.batterySaverEnabled).isFalse();
 
+        final PowerSaveState dataSaverState = mBatterySaverPolicy.getBatterySaverPolicy(
+                ServiceType.DATA_SAVER, BATTERY_SAVER_ON);
+        assertThat(dataSaverState.batterySaverEnabled).isTrue();
+
         final PowerSaveState gpsState =
                 mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.GPS, BATTERY_SAVER_ON);
         assertThat(gpsState.batterySaverEnabled).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index b5826f0..2003b91 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -26,8 +26,10 @@
 import android.view.Surface;
 import android.view.WindowManager;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -160,4 +162,22 @@
         sWm.mRoot.mOrientationChangeComplete = true;
         sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
     }
+
+    @Test
+    public void testGetOrientation() throws Exception {
+        final TestAppWindowToken token = new TestAppWindowToken(sDisplayContent);
+        token.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+        token.setFillsParent(false);
+        // Can not specify orientation if app doesn't fill parent.
+        assertEquals(SCREEN_ORIENTATION_UNSET, token.getOrientation());
+
+        token.setFillsParent(true);
+        token.hidden = true;
+        token.sendingToBottom = true;
+        // Can not specify orientation if app isn't visible even though it fills parent.
+        assertEquals(SCREEN_ORIENTATION_UNSET, token.getOrientation());
+        // Can specify orientation if the current orientation candidate is orientation behind.
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation(SCREEN_ORIENTATION_BEHIND));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 3868242..dd94a21 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -233,6 +234,26 @@
         assertEquals(currentOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
     }
 
+    @Test
+    public void testFocusedWindowMultipleDisplays() throws Exception {
+        // Create a focusable window and check that focus is calcualted correctly
+        final WindowState window1 =
+                createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "window1");
+        assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+
+        // Check that a new display doesn't affect focus
+        final DisplayContent dc = createNewDisplay();
+        assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+
+        // Add a window to the second display, and it should be focused
+        final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2");
+        assertEquals(window2, sWm.mRoot.computeFocusedWindow());
+
+        // Move the first window to the to including parents, and make sure focus is updated
+        window1.getParent().positionChildAt(POSITION_TOP, window1, true);
+        assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+    }
+
     private void assertForAllWindowsOrder(List<WindowState> expectedWindows) {
         final LinkedList<WindowState> actualWindows = new LinkedList();
 
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
index 3ce3df1..9dbd8a6 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
@@ -76,9 +76,9 @@
         task2.addChild(appWindowToken2, 0);
         appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
 
-        assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
         sWm.mClosingApps.add(appWindowToken2);
-        assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_LANDSCAPE);
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation());
     }
 
     @Test
@@ -94,9 +94,9 @@
         task2.addChild(appWindowToken2, 0);
         appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
 
-        assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
         task2.setSendingToBottom(true);
-        assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_LANDSCAPE);
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation());
     }
 
     @Test
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..a7d594c 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();
@@ -410,7 +422,7 @@
         final TestWindowContainer child1 = root.addChildWindow(builder);
         child1.setFillsParent(true);
 
-        assertTrue(root.getOrientation() == expectedOrientation);
+        assertEquals(expectedOrientation, root.getOrientation());
     }
 
     @Test
@@ -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();
         }
@@ -786,8 +805,13 @@
         }
 
         @Override
+        int getOrientation(int candidate) {
+            return mOrientation != null ? mOrientation : super.getOrientation(candidate);
+        }
+
+        @Override
         int getOrientation() {
-            return mOrientation != null ? mOrientation : super.getOrientation();
+            return getOrientation(super.mOrientation);
         }
 
         @Override
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..a9d930f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.view.View.VISIBLE;
+
 import android.app.ActivityManager.TaskDescription;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -233,7 +235,7 @@
         attrs.setTitle(name);
 
         final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE,
-                0, attrs, 0, 0, ownerCanAddInternalSystemWindow);
+                0, attrs, VISIBLE, 0, ownerCanAddInternalSystemWindow);
         // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
         // adding it to the token...
         token.addWindow(w);
@@ -303,17 +305,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 +432,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 +442,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/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index c790902..92233b1 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -125,8 +125,9 @@
     public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
 
     /**
-     * Extra key used to indicate the time (in millis) when the last outgoing emergency call was
-     * made.  This is used to identify potential emergency callbacks.
+     * Extra key used to indicate the time (in milliseconds since midnight, January 1, 1970 UTC)
+     * when the last outgoing emergency call was made.  This is used to identify potential emergency
+     * callbacks.
      */
     public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS =
             "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index dd23850..26c9430 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -23,8 +23,8 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.WorkerThread;
 import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
 import android.content.ContentResolver;
@@ -50,6 +50,7 @@
 
 import com.android.ims.internal.IImsServiceController;
 import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.ITelecomService;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.IPhoneSubInfo;
@@ -940,7 +941,11 @@
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns
+     * MEID for CDMA.
      */
+    @Deprecated
     public String getDeviceId() {
         try {
             ITelephony telephony = getITelephony();
@@ -962,7 +967,11 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *
      * @param slotIndex of which deviceID is returned
+     *
+     * @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns
+     * MEID for CDMA.
      */
+    @Deprecated
     public String getDeviceId(int slotIndex) {
         // FIXME this assumes phoneId == slotIndex
         try {
@@ -978,29 +987,25 @@
     }
 
     /**
-     * Returns the IMEI. Return null if IMEI is not available.
+     * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
+     * available.
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
-     *
-     * @hide
      */
-    @SystemApi
     public String getImei() {
         return getImei(getDefaultSim());
     }
 
     /**
-     * Returns the IMEI. Return null if IMEI is not available.
+     * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
+     * available.
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *
-     * @param slotIndex of which deviceID is returned
-     *
-     * @hide
+     * @param slotIndex of which IMEI is returned
      */
-    @SystemApi
     public String getImei(int slotIndex) {
         ITelephony telephony = getITelephony();
         if (telephony == null) return null;
@@ -1015,6 +1020,37 @@
     }
 
     /**
+     * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     */
+    public String getMeid() {
+        return getMeid(getDefaultSim());
+    }
+
+    /**
+     * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @param slotIndex of which MEID is returned
+     */
+    public String getMeid(int slotIndex) {
+        ITelephony telephony = getITelephony();
+        if (telephony == null) return null;
+
+        try {
+            return telephony.getMeidForSlot(slotIndex, getOpPackageName());
+        } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            return null;
+        }
+    }
+
+    /**
      * Returns the NAI. Return null if NAI is not available.
      *
      */
@@ -4056,9 +4092,19 @@
         return SubscriptionManager.getPhoneId(SubscriptionManager.getDefaultSubscriptionId());
     }
 
-    /** {@hide} */
+    /**
+     *  @return default SIM's slot index. If SIM is not inserted, return default SIM slot index.
+     *
+     * {@hide}
+     */
+    @VisibleForTesting
     public int getDefaultSim() {
-        return SubscriptionManager.getSlotIndex(SubscriptionManager.getDefaultSubscriptionId());
+        int slotIndex = SubscriptionManager.getSlotIndex(
+                SubscriptionManager.getDefaultSubscriptionId());
+        if (slotIndex == SubscriptionManager.SIM_NOT_INSERTED) {
+            slotIndex = SubscriptionManager.DEFAULT_SIM_SLOT_INDEX;
+        }
+        return slotIndex;
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 7613837..cd15c44 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1096,6 +1096,16 @@
     String getImeiForSlot(int slotIndex, String callingPackage);
 
     /**
+     * Returns the MEID for the given slot.
+     *
+     * @param slotIndex - device slot.
+     * @param callingPackage The package making the call.
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     */
+    String getMeidForSlot(int slotIndex, String callingPackage);
+
+    /**
      * Returns the device software version.
      *
      * @param slotIndex - device slot.
diff --git a/tests/testables/src/android/testing/AndroidTestingRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java
index a425f70..816ed03 100644
--- a/tests/testables/src/android/testing/AndroidTestingRunner.java
+++ b/tests/testables/src/android/testing/AndroidTestingRunner.java
@@ -18,7 +18,7 @@
 import android.support.test.internal.runner.junit4.statement.RunBefores;
 import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
 
-import android.testing.TestableLooper.LooperFrameworkMethod;
+import android.testing.TestableLooper.LooperStatement;
 import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.After;
@@ -30,7 +30,6 @@
 import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.Statement;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -50,21 +49,28 @@
 
     @Override
     protected Statement methodInvoker(FrameworkMethod method, Object test) {
-        method = looperWrap(method, test, method);
-        final Statement statement = super.methodInvoker(method, test);
-        return shouldRunOnUiThread(method) ? new UiThreadStatement(statement, true) : statement;
+        return shouldRunOnUiThread(method) ? new UiThreadStatement(
+                methodInvokerInt(method, test), true) : methodInvokerInt(method, test);
+    }
+
+    protected Statement methodInvokerInt(FrameworkMethod method, Object test) {
+        RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
+        if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
+        if (annotation != null) {
+            return new LooperStatement(super.methodInvoker(method, test),
+                    annotation.setAsMainLooper(), test);
+        }
+        return super.methodInvoker(method, test);
     }
 
     protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
-        List befores = looperWrap(method, target,
-                this.getTestClass().getAnnotatedMethods(Before.class));
+        List befores = this.getTestClass().getAnnotatedMethods(Before.class);
         return befores.isEmpty() ? statement : new RunBefores(method, statement,
                 befores, target);
     }
 
     protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
-        List afters = looperWrap(method, target,
-                this.getTestClass().getAnnotatedMethods(After.class));
+        List afters = this.getTestClass().getAnnotatedMethods(After.class);
         return afters.isEmpty() ? statement : new RunAfters(method, statement, afters,
                 target);
     }
@@ -82,30 +88,6 @@
         return annotation == null ? 0L : annotation.timeout();
     }
 
-    protected List<FrameworkMethod> looperWrap(FrameworkMethod method, Object test,
-            List<FrameworkMethod> methods) {
-        RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
-        if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
-        if (annotation != null) {
-            methods = new ArrayList<>(methods);
-            for (int i = 0; i < methods.size(); i++) {
-                methods.set(i, LooperFrameworkMethod.get(methods.get(i),
-                        annotation.setAsMainLooper(), test));
-            }
-        }
-        return methods;
-    }
-
-    protected FrameworkMethod looperWrap(FrameworkMethod method, Object test,
-            FrameworkMethod base) {
-        RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
-        if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
-        if (annotation != null) {
-            return LooperFrameworkMethod.get(base, annotation.setAsMainLooper(), test);
-        }
-        return base;
-    }
-
     public boolean shouldRunOnUiThread(FrameworkMethod method) {
         if (mKlass.getAnnotation(UiThreadTest.class) != null) {
             return true;
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 62490bc..8a33cf9 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -15,21 +15,20 @@
 package android.testing;
 
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
-import android.os.TestLooperManager;
-import android.support.test.InstrumentationRegistry;
 import android.util.ArrayMap;
 
-import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 import java.util.Map;
 
 /**
@@ -39,35 +38,65 @@
  */
 public class TestableLooper {
 
+    private final Method mNext;
+    private final Method mRecycleUnchecked;
+
     private Looper mLooper;
     private MessageQueue mQueue;
     private boolean mMain;
     private Object mOriginalMain;
     private MessageHandler mMessageHandler;
 
+    private int mParsedCount;
     private Handler mHandler;
     private Message mEmptyMessage;
-    private TestLooperManager mQueueWrapper;
 
-    public TestableLooper(Looper l) throws Exception {
-        this(InstrumentationRegistry.getInstrumentation().acquireLooperManager(l), l);
+    public TestableLooper() throws Exception {
+        this(true);
     }
 
-    private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception {
-        mQueueWrapper = wrapper;
-        setupQueue(l);
-    }
-
-    private TestableLooper(Looper looper, boolean b) throws Exception {
-        setupQueue(looper);
+    public TestableLooper(boolean setMyLooper) throws Exception {
+        setupQueue(setMyLooper);
+        mNext = mQueue.getClass().getDeclaredMethod("next");
+        mNext.setAccessible(true);
+        mRecycleUnchecked = Message.class.getDeclaredMethod("recycleUnchecked");
+        mRecycleUnchecked.setAccessible(true);
     }
 
     public Looper getLooper() {
         return mLooper;
     }
 
-    private void setupQueue(Looper l) throws Exception {
-        mLooper = l;
+    private void clearLooper() throws NoSuchFieldException, IllegalAccessException {
+        Field field = Looper.class.getDeclaredField("sThreadLocal");
+        field.setAccessible(true);
+        ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null);
+        sThreadLocal.set(null);
+    }
+
+    private boolean setForCurrentThread() throws NoSuchFieldException, IllegalAccessException {
+        if (Looper.myLooper() != mLooper) {
+            Field field = Looper.class.getDeclaredField("sThreadLocal");
+            field.setAccessible(true);
+            ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null);
+            sThreadLocal.set(mLooper);
+            return true;
+        }
+        return false;
+    }
+
+    private void setupQueue(boolean setMyLooper) throws Exception {
+        if (setMyLooper) {
+            clearLooper();
+            Looper.prepare();
+            mLooper = Looper.myLooper();
+        } else {
+            Constructor<Looper> constructor = Looper.class.getDeclaredConstructor(
+                    boolean.class);
+            constructor.setAccessible(true);
+            mLooper = constructor.newInstance(true);
+        }
+
         mQueue = mLooper.getQueue();
         mHandler = new Handler(mLooper);
     }
@@ -92,7 +121,9 @@
      * tests.
      */
     public void destroy() throws NoSuchFieldException, IllegalAccessException {
-        mQueueWrapper.release();
+        if (Looper.myLooper() == mLooper) {
+            clearLooper();
+        }
         if (mMain && mOriginalMain != null) {
             Field field = mLooper.getClass().getDeclaredField("sMainLooper");
             field.setAccessible(true);
@@ -133,26 +164,26 @@
 
     private boolean parseMessageInt() {
         try {
-            Message result = mQueueWrapper.next();
+            Message result = (Message) mNext.invoke(mQueue);
             if (result != null) {
                 // This is a break message.
                 if (result == mEmptyMessage) {
-                    mQueueWrapper.recycle(result);
+                    mRecycleUnchecked.invoke(result);
                     return false;
                 }
 
                 if (mMessageHandler != null) {
                     if (mMessageHandler.onMessageHandled(result)) {
                         result.getTarget().dispatchMessage(result);
-                        mQueueWrapper.recycle(result);
+                        mRecycleUnchecked.invoke(result);
                     } else {
-                        mQueueWrapper.recycle(result);
+                        mRecycleUnchecked.invoke(result);
                         // Message handler indicated it doesn't want us to continue.
                         return false;
                     }
                 } else {
                     result.getTarget().dispatchMessage(result);
-                    mQueueWrapper.recycle(result);
+                    mRecycleUnchecked.invoke(result);
                 }
             } else {
                 // No messages, don't continue parsing
@@ -168,14 +199,10 @@
      * Runs an executable with myLooper set and processes all messages added.
      */
     public void runWithLooper(RunnableWithException runnable) throws Exception {
-        new Handler(getLooper()).post(() -> {
-            try {
-                runnable.run();
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        });
+        boolean set = setForCurrentThread();
+        runnable.run();
         processAllMessages();
+        if (set) clearLooper();
     }
 
     public interface RunnableWithException {
@@ -194,131 +221,33 @@
         return sLoopers.get(test);
     }
 
-    public static class LooperFrameworkMethod extends FrameworkMethod {
-        private HandlerThread mHandlerThread;
+    public static class LooperStatement extends Statement {
+        private final boolean mSetAsMain;
+        private final Statement mBase;
+        private final TestableLooper mLooper;
 
-        private final TestableLooper mTestableLooper;
-        private final Looper mLooper;
-        private final Handler mHandler;
-
-        public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
-            super(base.getMethod());
+        public LooperStatement(Statement base, boolean setAsMain, Object test) {
+            mBase = base;
             try {
-                mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
-                mTestableLooper = new TestableLooper(mLooper, false);
+                mLooper = new TestableLooper(false);
+                sLoopers.put(test, mLooper);
+                mSetAsMain = setAsMain;
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
-            sLoopers.put(test, mTestableLooper);
-            mHandler = new Handler(mLooper);
-        }
-
-        public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) {
-            super(base.getMethod());
-            mLooper = other.mLooper;
-            mTestableLooper = other;
-            mHandler = new Handler(mLooper);
-        }
-
-        public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) {
-            if (sLoopers.containsKey(test)) {
-                return new LooperFrameworkMethod(sLoopers.get(test), base);
-            }
-            return new LooperFrameworkMethod(base, setAsMain, test);
         }
 
         @Override
-        public Object invokeExplosively(Object target, Object... params) throws Throwable {
-            if (Looper.myLooper() == mLooper) {
-                // Already on the right thread from another statement, just execute then.
-                return super.invokeExplosively(target, params);
+        public void evaluate() throws Throwable {
+            mLooper.setForCurrentThread();
+            if (mSetAsMain) {
+                mLooper.setAsMainLooper();
             }
-            boolean set = mTestableLooper.mQueueWrapper == null;
-            if (set) {
-                mTestableLooper.mQueueWrapper = InstrumentationRegistry.getInstrumentation()
-                        .acquireLooperManager(mLooper);
-            }
+
             try {
-                Object[] ret = new Object[1];
-                // Run the execution on the looper thread.
-                Runnable execute = () -> {
-                    try {
-                        ret[0] = super.invokeExplosively(target, params);
-                    } catch (Throwable throwable) {
-                        throw new LooperException(throwable);
-                    }
-                };
-                mHandler.post(execute);
-                // Try to wait for the message to be queued.
-                for (int i = 0; i < 10; i++) {
-                    if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
-                        Thread.sleep(1);
-                    }
-                }
-                if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
-                    throw new RuntimeException("Message didn't queue...");
-                }
-                Message m = mTestableLooper.mQueueWrapper.next();
-                // Parse all other messages until we get to ours.
-                while (m.getTarget() != mHandler) {
-                    try {
-                        mTestableLooper.mQueueWrapper.execute(m);
-                    } catch (LooperException e) {
-                        throw e.getSource();
-                    } finally {
-                        mTestableLooper.mQueueWrapper.recycle(m);
-                    }
-                    m = mTestableLooper.mQueueWrapper.next();
-                }
-                // Dispatch our message.
-                try {
-                    mTestableLooper.mQueueWrapper.execute(m);
-                } catch (LooperException e) {
-                    throw e.getSource();
-                } catch (RuntimeException re) {
-                    // If the TestLooperManager has to post, it will wrap what it throws in a
-                    // RuntimeException, make sure we grab the actual source.
-                    if (re.getCause() instanceof LooperException) {
-                        throw ((LooperException) re.getCause()).getSource();
-                    } else {
-                        throw re.getCause();
-                    }
-                } finally {
-                    mTestableLooper.mQueueWrapper.recycle(m);
-                }
-                return ret[0];
+                mBase.evaluate();
             } finally {
-                if (set) {
-                    mTestableLooper.mQueueWrapper.release();
-                    mTestableLooper.mQueueWrapper = null;
-                }
-            }
-        }
-
-        private Looper createLooper() {
-            // TODO: Find way to share these.
-            mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
-            mHandlerThread.start();
-            return mHandlerThread.getLooper();
-        }
-
-        @Override
-        protected void finalize() throws Throwable {
-            super.finalize();
-            if (mHandlerThread != null) {
-                mHandlerThread.quit();
-            }
-        }
-
-        private static class LooperException extends RuntimeException {
-            private final Throwable mSource;
-
-            public LooperException(Throwable t) {
-                mSource = t;
-            }
-
-            public Throwable getSource() {
-                return mSource;
+                mLooper.destroy();
             }
         }
     }
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 12f1d0a..18e5fff 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -24,16 +24,17 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.testing.TestableLooper.MessageHandler;
 import android.testing.TestableLooper.RunWithLooper;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class TestableLooperTest {
@@ -45,6 +46,11 @@
         mTestableLooper = TestableLooper.get(this);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mTestableLooper.destroy();
+    }
+
     @Test
     public void testMessageExecuted() throws Exception {
         Handler h = new Handler();
@@ -127,23 +133,39 @@
     @Test
     public void testMainLooper() throws Exception {
         assertNotEquals(Looper.myLooper(), Looper.getMainLooper());
+
+        Looper originalMain = Looper.getMainLooper();
+        mTestableLooper.setAsMainLooper();
+        assertEquals(Looper.myLooper(), Looper.getMainLooper());
+        Runnable r = mock(Runnable.class);
+
+        new Handler(Looper.getMainLooper()).post(r);
+        mTestableLooper.processAllMessages();
+
+        verify(r).run();
+        mTestableLooper.destroy();
+
+        assertEquals(originalMain, Looper.getMainLooper());
+    }
+
+    @Test
+    public void testNotMyLooper() throws Exception {
+        TestableLooper looper = new TestableLooper(false);
+
+        assertEquals(Looper.myLooper(), mTestableLooper.getLooper());
+        assertNotEquals(Looper.myLooper(), looper.getLooper());
+
         Runnable r = mock(Runnable.class);
         Runnable r2 = mock(Runnable.class);
-        TestableLooper testableLooper = new TestableLooper(Looper.getMainLooper());
+        new Handler().post(r);
+        new Handler(looper.getLooper()).post(r2);
 
-        try {
-            testableLooper.setMessageHandler(m -> {
-                if (m.getCallback() == r) return true;
-                return false;
-            });
-            new Handler(Looper.getMainLooper()).post(r);
-            testableLooper.processAllMessages();
+        looper.processAllMessages();
+        verify(r2).run();
+        verify(r, never()).run();
 
-            verify(r).run();
-            verify(r2, never()).run();
-        } finally {
-            testableLooper.destroy();
-        }
+        mTestableLooper.processAllMessages();
+        verify(r).run();
     }
 
     @Test
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/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 3b88290..2e14974 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -38,6 +38,7 @@
 import android.content.res.BridgeAssetManager;
 import android.graphics.Bitmap;
 import android.graphics.FontFamily_Delegate;
+import android.graphics.Typeface;
 import android.graphics.Typeface_Delegate;
 import android.icu.util.ULocale;
 import android.os.Looper;
@@ -402,6 +403,7 @@
 
         // dispose of the default typeface.
         Typeface_Delegate.resetDefaults();
+        Typeface.sDynamicTypefaceCache.evictAll();
 
         return true;
     }
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/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
index 67b42a7..00dddee 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -311,6 +311,7 @@
         sFrameworkRepo = null;
         sProjectResources = null;
         sLogger = null;
+        sBridge.dispose();
         sBridge = null;
 
         TestUtils.gc();
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index b0aa3c2..cb0bc6d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -334,7 +334,8 @@
     private final static String[] PROMOTED_FIELDS = new String[] {
         "android.graphics.drawable.VectorDrawable#mVectorState",
         "android.view.Choreographer#mLastFrameTimeNanos",
-        "android.graphics.FontFamily#mBuilderPtr"
+        "android.graphics.FontFamily#mBuilderPtr",
+        "android.graphics.Typeface#sDynamicTypefaceCache"
     };
 
     /**
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();
         }