Merge "Fix permission checking for a11y volume."
diff --git a/Android.mk b/Android.mk
index 749242d..8daab8f 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 \
@@ -567,8 +567,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES :=                          \
     framework-protos                                    \
-    android.hardware.thermal@1.0-java-constants         \
     android.hardware.health@1.0-java-constants          \
+    android.hardware.thermal@1.0-java-constants         \
+    android.hardware.tv.input@1.0-java-constants        \
     android.hardware.usb@1.0-java-constants             \
     android.hardware.vibrator@1.0-java-constants        \
 
diff --git a/api/current.txt b/api/current.txt
index 8f969c1..8d27905 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21818,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 {
@@ -21847,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 {
@@ -22275,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);
@@ -23586,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);
   }
@@ -24326,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 {
@@ -34858,7 +34869,7 @@
     field public static final java.lang.String WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN = "wifi_device_owner_configs_lockdown";
     field public static final java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
     field public static final java.lang.String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = "wifi_mobile_data_transition_wakelock_timeout_ms";
-    field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
+    field public static final deprecated java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
     field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = "wifi_networks_available_repeat_delay";
     field public static final java.lang.String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
     field public static final java.lang.String WIFI_ON = "wifi_on";
@@ -37842,6 +37853,7 @@
     method public void onRangeStart(java.lang.String, int, int, int);
     method public abstract void onStart(java.lang.String);
     method public void onStop(java.lang.String, boolean);
+    method public deprecated void onUtteranceRangeStart(java.lang.String, int, int);
   }
 
   public class Voice implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 48b878e..badd31e 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";
@@ -23604,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 {
@@ -23633,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 {
@@ -24061,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);
@@ -25383,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);
   }
@@ -26195,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 {
@@ -37748,7 +37760,6 @@
     field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
     field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
     field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
-    field public static final java.lang.String ACTION_CONFIGURE_WIFI_SETTINGS = "android.settings.CONFIGURE_WIFI_SETTINGS";
     field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
@@ -37798,7 +37809,6 @@
     field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS";
     field public static final java.lang.String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS";
-    field public static final java.lang.String ACTION_WIFI_SAVED_NETWORK_SETTINGS = "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS";
     field public static final java.lang.String ACTION_WIRELESS_SETTINGS = "android.settings.WIRELESS_SETTINGS";
     field public static final java.lang.String ACTION_ZEN_MODE_PRIORITY_SETTINGS = "android.settings.ZEN_MODE_PRIORITY_SETTINGS";
@@ -37871,7 +37881,7 @@
     field public static final java.lang.String WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN = "wifi_device_owner_configs_lockdown";
     field public static final java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
     field public static final java.lang.String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = "wifi_mobile_data_transition_wakelock_timeout_ms";
-    field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
+    field public static final deprecated java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
     field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = "wifi_networks_available_repeat_delay";
     field public static final java.lang.String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
     field public static final java.lang.String WIFI_ON = "wifi_on";
@@ -40976,6 +40986,7 @@
     method public void onRangeStart(java.lang.String, int, int, int);
     method public abstract void onStart(java.lang.String);
     method public void onStop(java.lang.String, boolean);
+    method public deprecated void onUtteranceRangeStart(java.lang.String, int, int);
   }
 
   public class Voice implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4d8d7f2..3a23cd9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4031,6 +4031,8 @@
     method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
     method public android.app.ActivityOptions setLaunchDisplayId(int);
     method public void setLaunchStackId(int);
+    method public void setLaunchTaskId(int);
+    method public void setTaskOverlay(boolean, boolean);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -11750,7 +11752,7 @@
   }
 
   public final class PageViewCursor extends android.database.CursorWrapper implements android.database.CrossProcessCursor {
-    ctor public PageViewCursor(android.database.Cursor, int, int);
+    ctor public PageViewCursor(android.database.Cursor, android.os.Bundle);
     method public void fillWindow(int, android.database.CursorWindow);
     method public android.database.CursorWindow getWindow();
     method public boolean onMove(int, int);
@@ -21929,23 +21931,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 {
@@ -21958,7 +21960,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 {
@@ -22386,7 +22403,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);
@@ -23697,10 +23714,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);
   }
@@ -24437,9 +24450,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 {
@@ -34997,7 +35010,7 @@
     field public static final java.lang.String WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN = "wifi_device_owner_configs_lockdown";
     field public static final java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
     field public static final java.lang.String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = "wifi_mobile_data_transition_wakelock_timeout_ms";
-    field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
+    field public static final deprecated java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
     field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = "wifi_networks_available_repeat_delay";
     field public static final java.lang.String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
     field public static final java.lang.String WIFI_ON = "wifi_on";
@@ -38043,6 +38056,7 @@
     method public void onRangeStart(java.lang.String, int, int, int);
     method public abstract void onStart(java.lang.String);
     method public void onStop(java.lang.String, boolean);
+    method public deprecated void onUtteranceRangeStart(java.lang.String, int, int);
   }
 
   public class Voice implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 0b9479d..aa7cdf7 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1068,6 +1068,7 @@
      * Sets the task the activity will be launched in.
      * @hide
      */
+    @TestApi
     public void setLaunchTaskId(int taskId) {
         mLaunchTaskId = taskId;
     }
@@ -1085,6 +1086,7 @@
      * the task will also not be moved to the front of the stack.
      * @hide
      */
+    @TestApi
     public void setTaskOverlay(boolean taskOverlay, boolean canResume) {
         mTaskOverlay = taskOverlay;
         mTaskOverlayCanResume = canResume;
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 9e2eb84..aa61ce2 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -135,8 +135,8 @@
     /**
      * 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
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index c991e2f..b35a593 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -59,8 +59,8 @@
     /**
      * 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
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 97%
rename from core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
rename to core/java/android/bluetooth/IBluetoothGattCallback.aidl
index ed69e54..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);
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
similarity index 97%
rename from core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
rename to core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
index 267e882..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);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1fafe65..d264e09 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4400,8 +4400,12 @@
             defaultMaxAspectRatio = owner.applicationInfo.maxAspectRatio;
         }
 
-        aInfo.maxAspectRatio = Math.max(1.0f, sa.getFloat(
-                R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio));
+        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;
+        }
     }
 
     /**
diff --git a/core/java/android/database/PageViewCursor.java b/core/java/android/database/PageViewCursor.java
index 44727a0..4569a27 100644
--- a/core/java/android/database/PageViewCursor.java
+++ b/core/java/android/database/PageViewCursor.java
@@ -15,6 +15,7 @@
  */
 package android.database;
 
+import static com.android.internal.util.ArrayUtils.contains;
 import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.annotation.Nullable;
@@ -25,7 +26,7 @@
 import android.util.Log;
 import android.util.MathUtils;
 
-import com.android.internal.util.ArrayUtils;
+import java.util.Arrays;
 
 /**
  * Cursor wrapper that provides visibility into a subset of a wrapped cursor.
@@ -40,11 +41,12 @@
     /** An extra added to results that are auto-paged using the wrapper. */
     public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED";
 
+    private static final String[] EMPTY_ARGS = new String[0];
     private static final String TAG = "PageViewCursor";
     private static final boolean DEBUG = Build.IS_DEBUGGABLE;
     private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
 
-    private final int mOffset;  // aka first index
+    private final int mOffset; // aka first index
     private final int mCount;
     private final Bundle mExtras;
 
@@ -55,12 +57,17 @@
     /**
      * @see PageViewCursor#wrap(Cursor, Bundle)
      */
-    public PageViewCursor(Cursor cursor, int offset, int limit) {
+    public PageViewCursor(Cursor cursor, Bundle queryArgs) {
         super(cursor);
 
+        int offset = queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0);
+        int limit = queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE);
+
         checkArgument(offset > -1);
         checkArgument(limit > -1);
 
+        int count = mCursor.getCount();
+
         mOffset = offset;
 
         mExtras = new Bundle();
@@ -68,26 +75,47 @@
         if (extras != null) {
             mExtras.putAll(extras);
         }
-        mExtras.putBoolean(EXTRA_AUTO_PAGED, true);
-
-        // We need a mutable bundle so we can add QUERY_RESULT_SIZE.
-        // Direct equality check is correct here. Bundle.EMPTY is a specific instance
-        // of Bundle that is immutable by way of implementation.
-        // mExtras = (extras == Bundle.EMPTY) ? new Bundle() : extras;
 
         // When we're wrapping another cursor, it should not already be "paged".
-        checkArgument(!mExtras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE));
+        checkArgument(!hasPagedResponseDetails(mExtras));
 
-        int count = mCursor.getCount();
+        mExtras.putBoolean(EXTRA_AUTO_PAGED, true);
         mExtras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, count);
 
+        // Ensure we retain any extra args supplied in cursor extras, and add
+        // offset and/or limit.
+        String[] existingArgs = mExtras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
+        existingArgs = existingArgs != null ? existingArgs : EMPTY_ARGS;
+
+        int size = existingArgs.length;
+
+        // copy the array with space for the extra query args we'll be adding.
+        String[] newArgs = Arrays.copyOf(existingArgs, size + 2);
+
+        if (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)) {
+            newArgs[size++] = ContentResolver.QUERY_ARG_OFFSET;
+        }
+        if (queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)) {
+            newArgs[size++] = ContentResolver.QUERY_ARG_LIMIT;
+        }
+
+        assert(size > existingArgs.length);  // must add at least one arg.
+
+        // At this point there may be a null element at the end of
+        // the array because our pre-sizing didn't match the actualy
+        // number of args we added. So we trim.
+        if (size == newArgs.length - 1) {
+            newArgs = Arrays.copyOf(newArgs, size);
+        }
+        mExtras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, newArgs);
+
         mCount = MathUtils.constrain(count - offset, 0, limit);
 
         if (DEBUG) Log.d(TAG, "Wrapped cursor"
-            + " offset: " + mOffset
-            + ", limit: " + limit
-            + ", delegate_size: " + count
-            + ", paged_count: " + mCount);
+                + " offset: " + mOffset
+                + ", limit: " + limit
+                + ", delegate_size: " + count
+                + ", paged_count: " + mCount);
     }
 
     @Override
@@ -155,9 +183,9 @@
     public boolean moveToPosition(int position) {
         if (position >= mCount) {
             if (VERBOSE) Log.v(TAG, "Invalid Positon: " + position + " >= count: " + mCount
-                    + ". Moving to last record.");
+                        + ". Moving to last record.");
             mPos = mCount;
-            super.moveToPosition(mOffset + mPos);  // move into "after last" state.
+            super.moveToPosition(mOffset + mPos); // move into "after last" state.
             return false;
         }
 
@@ -198,15 +226,15 @@
 
     @Override
     public boolean getWantsAllOnMoveCalls() {
-        return false;  // we want bulk cursor adapter to lift data into a CursorWindow.
+        return false; // we want bulk cursor adapter to lift data into a CursorWindow.
     }
 
     @Override
     public CursorWindow getWindow() {
         assert(mPos == -1 || mPos == 0);
         if (mWindow == null) {
-           mWindow = new CursorWindow("PageViewCursorWindow");
-           fillWindow(0, mWindow);
+            mWindow = new CursorWindow("PageViewCursorWindow");
+            fillWindow(0, mWindow);
         }
 
         return mWindow;
@@ -224,17 +252,16 @@
     }
 
     /**
-     * Wraps the cursor such that it will honor paging args (if present), AND if the cursor
-     * does not report paging size.
-     *
-     * <p>No-op if cursor already contains paging or is less than specified page size.
+     * Wraps the cursor such that it will honor paging args (if present), AND if the cursor does
+     * not report paging size.
+     * <p>
+     * No-op if cursor already contains paging or is less than specified page size.
      */
     public static Cursor wrap(Cursor cursor, @Nullable Bundle queryArgs) {
 
-        boolean hasPagingArgs =
-                queryArgs != null
+        boolean hasPagingArgs = queryArgs != null
                 && (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)
-                || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT));
+                        || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT));
 
         if (!hasPagingArgs) {
             if (VERBOSE) Log.v(TAG, "No-wrap: No paging args in request.");
@@ -253,25 +280,26 @@
             return cursor;
         }
 
-        return new PageViewCursor(
-                cursor,
-                queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0),
-                queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE));
+        return new PageViewCursor(cursor, queryArgs);
     }
 
     /**
-     * @return true if the extras contains information indicating the associated
-     * cursor is paged.
+     * @return true if the extras contains information indicating the associated cursor is
+     *         paged.
      */
     private static boolean hasPagedResponseDetails(@Nullable Bundle extras) {
-        if (extras != null && extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)) {
+        if (extras == null) {
+            return false;
+        }
+
+        if (extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)) {
             return true;
         }
 
         String[] honoredArgs = extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
-        if (honoredArgs != null && (
-                ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET)
-                || ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) {
+        if (honoredArgs != null
+                && (contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET)
+                        || contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) {
             return true;
         }
 
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index dcd069d..da771e4 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -118,6 +118,11 @@
      * the Surface provided to prepare must not be used as a target of a CaptureRequest submitted
      * to this session.</p>
      *
+     * <p>Note that if 2 surfaces share the same stream via {@link
+     * OutputConfiguration#enableSurfaceSharing} and {@link OutputConfiguration#addSurface},
+     * prepare() only needs to be called on one surface, and {link
+     * StateCallback#onSurfacePrepared} will be triggered for both surfaces.</p>
+     *
      * <p>{@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}
      * devices cannot pre-allocate output buffers; for those devices,
      * {@link StateCallback#onSurfacePrepared} will be immediately called, and no preallocation is
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e3b97e8..e75b375 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -708,7 +708,8 @@
         synchronized(mInterfaceLock) {
             int streamId = -1;
             for (int i = 0; i < mConfiguredOutputs.size(); i++) {
-                if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
+                final List<Surface> surfaces = mConfiguredOutputs.valueAt(i).getSurfaces();
+                if (surfaces.contains(surface)) {
                     streamId = mConfiguredOutputs.keyAt(i);
                     break;
                 }
@@ -2020,9 +2021,10 @@
                 Log.w(TAG, "onPrepared invoked for unknown output Surface");
                 return;
             }
-            final Surface surface = output.getSurface();
-
-            sessionCallback.onSurfacePrepared(surface);
+            final List<Surface> surfaces = output.getSurfaces();
+            for (Surface surface : surfaces) {
+                sessionCallback.onSurfacePrepared(surface);
+            }
         }
 
         @Override
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 70ecf89..29483cd 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -120,6 +120,14 @@
             "android.net.wifi.use_open_wifi_package";
 
     /**
+     * Meta-data specified on a {@link NetworkRecommendationProvider} that specifies the
+     * {@link android.app.NotificationChannel} ID used to post open network notifications.
+     * @hide
+     */
+    public static final String NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID_META_DATA =
+            "android.net.wifi.notification_channel_id_network_available";
+
+    /**
      * Broadcast action: the active scorer has been changed. Scorer apps may listen to this to
      * perform initialization once selected as the active scorer, or clean up unneeded resources
      * if another scorer has been selected. This is an explicit broadcast only sent to the
diff --git a/core/java/android/net/NetworkScorerAppData.java b/core/java/android/net/NetworkScorerAppData.java
index 5bf1e10..1734b34 100644
--- a/core/java/android/net/NetworkScorerAppData.java
+++ b/core/java/android/net/NetworkScorerAppData.java
@@ -23,13 +23,20 @@
      * wifi networks automatically" feature.
      */
     private final ComponentName mEnableUseOpenWifiActivity;
+    /**
+     * The {@link android.app.NotificationChannel} ID used by {@link #mRecommendationService} to
+     * post open network notifications.
+     */
+    private final String mNetworkAvailableNotificationChannelId;
 
     public NetworkScorerAppData(int packageUid, ComponentName recommendationServiceComp,
-            String recommendationServiceLabel, ComponentName enableUseOpenWifiActivity) {
+            String recommendationServiceLabel, ComponentName enableUseOpenWifiActivity,
+            String networkAvailableNotificationChannelId) {
         this.packageUid = packageUid;
         this.mRecommendationService = recommendationServiceComp;
         this.mRecommendationServiceLabel = recommendationServiceLabel;
         this.mEnableUseOpenWifiActivity = enableUseOpenWifiActivity;
+        this.mNetworkAvailableNotificationChannelId = networkAvailableNotificationChannelId;
     }
 
     protected NetworkScorerAppData(Parcel in) {
@@ -37,6 +44,7 @@
         mRecommendationService = ComponentName.readFromParcel(in);
         mRecommendationServiceLabel = in.readString();
         mEnableUseOpenWifiActivity = ComponentName.readFromParcel(in);
+        mNetworkAvailableNotificationChannelId = in.readString();
     }
 
     @Override
@@ -45,6 +53,7 @@
         ComponentName.writeToParcel(mRecommendationService, dest);
         dest.writeString(mRecommendationServiceLabel);
         ComponentName.writeToParcel(mEnableUseOpenWifiActivity, dest);
+        dest.writeString(mNetworkAvailableNotificationChannelId);
     }
 
     @Override
@@ -83,6 +92,11 @@
         return mRecommendationServiceLabel;
     }
 
+    @Nullable
+    public String getNetworkAvailableNotificationChannelId() {
+        return mNetworkAvailableNotificationChannelId;
+    }
+
     @Override
     public String toString() {
         return "NetworkScorerAppData{" +
@@ -90,6 +104,8 @@
                 ", mRecommendationService=" + mRecommendationService +
                 ", mRecommendationServiceLabel=" + mRecommendationServiceLabel +
                 ", mEnableUseOpenWifiActivity=" + mEnableUseOpenWifiActivity +
+                ", mNetworkAvailableNotificationChannelId=" +
+                mNetworkAvailableNotificationChannelId +
                 '}';
     }
 
@@ -101,12 +117,14 @@
         return packageUid == that.packageUid &&
                 Objects.equals(mRecommendationService, that.mRecommendationService) &&
                 Objects.equals(mRecommendationServiceLabel, that.mRecommendationServiceLabel) &&
-                Objects.equals(mEnableUseOpenWifiActivity, that.mEnableUseOpenWifiActivity);
+                Objects.equals(mEnableUseOpenWifiActivity, that.mEnableUseOpenWifiActivity) &&
+                Objects.equals(mNetworkAvailableNotificationChannelId,
+                        that.mNetworkAvailableNotificationChannelId);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(packageUid, mRecommendationService, mRecommendationServiceLabel,
-                mEnableUseOpenWifiActivity);
+                mEnableUseOpenWifiActivity, mNetworkAvailableNotificationChannelId);
     }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7005d44..5820211 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -396,38 +396,6 @@
             "android.settings.WIFI_IP_SETTINGS";
 
     /**
-     * Activity Action: Show settings to allow the configuration of Wi-Fi features.
-     * <p>
-     * In some cases, a matching Activity may not exist, so ensure you
-     * safeguard against this.
-     * <p>
-     * Input: Nothing.
-     * <p>
-     * Output: Nothing.
-     * @hide
-     */
-    @SystemApi
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_CONFIGURE_WIFI_SETTINGS =
-            "android.settings.CONFIGURE_WIFI_SETTINGS";
-
-    /**
-     * Activity Action: Show settings to allow configuration of Wi-Fi saved networks.
-     * <p>
-     * In some cases, a matching Activity may not exist, so ensure you
-     * safeguard against this.
-     * <p>
-     * Input: Nothing.
-     * <p>
-     * Output: Nothing.
-     * @hide
-     */
-    @SystemApi
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_WIFI_SAVED_NETWORK_SETTINGS =
-            "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
-
-    /**
      * Activity Action: Show settings to allow configuration of Bluetooth.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -8232,9 +8200,14 @@
         * the open network(s) disappear, we remove the notification. When we
         * show the notification, we will not show it again for
         * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
+        *
+        * @deprecated This feature is no longer controlled by this setting in
+        * {@link android.os.Build.VERSION_CODES#O}.
         */
+       @Deprecated
        public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
                "wifi_networks_available_notification_on";
+
        /**
         * {@hide}
         */
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index e59ec08..59ee8f3 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -137,7 +137,15 @@
      * @param end The end index of the range (exclusive) in the utterance text.
      * @param frame The position in frames in the audio of the request where this range is spoken.
      */
-    public void onRangeStart(String utteranceId, int start, int end, int frame) {}
+    public void onRangeStart(String utteranceId, int start, int end, int frame) {
+        onUtteranceRangeStart(utteranceId, start, end);
+    }
+
+    /**
+     * @deprecated Due to internal API changes. Remove when apps catch up.
+     */
+    public void onUtteranceRangeStart(String utteranceId, int start, int end) {
+    }
 
     /**
      * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new progress listener.
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/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index be12f57..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,12 +119,14 @@
         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, classified);
+                            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", classificationResult));
@@ -208,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 "";
@@ -262,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/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 7d243af..59fb02d 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -2064,7 +2064,7 @@
         }
 
         if (update) {
-            update(mAnchor.get(), p);
+            update(mAnchor != null ? mAnchor.get() : null, p);
         }
     }
 
@@ -2371,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);
@@ -2482,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,
@@ -2498,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) {
@@ -2544,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/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java
index 6d814bf..d2a9072 100644
--- a/core/java/com/android/internal/widget/SwipeDismissLayout.java
+++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java
@@ -26,6 +26,7 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ReceiverCallNotAllowedException;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -86,24 +87,7 @@
 
     private OnDismissedListener mDismissedListener;
     private OnSwipeProgressChangedListener mProgressListener;
-    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
-        private Runnable mRunnable = new Runnable() {
-            @Override
-            public void run() {
-                if (mDismissed) {
-                    dismiss();
-                } else {
-                    cancel();
-                }
-                resetMembers();
-            }
-        };
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            post(mRunnable);
-        }
-    };
+    private BroadcastReceiver mScreenOffReceiver;
     private IntentFilter mScreenOffFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
 
 
@@ -146,12 +130,36 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        getContext().registerReceiver(mScreenOffReceiver, mScreenOffFilter);
+        try {
+            mScreenOffReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    post(() -> {
+                        if (mDismissed) {
+                            dismiss();
+                        } else {
+                            cancel();
+                        }
+                        resetMembers();
+                    });
+                }
+            };
+            getContext().registerReceiver(mScreenOffReceiver, mScreenOffFilter);
+        } catch (ReceiverCallNotAllowedException e) {
+            /* Exception is thrown if the context is a ReceiverRestrictedContext object. As
+             * ReceiverRestrictedContext is not public, the context type cannot be checked before
+             * calling registerReceiver. The most likely scenario in which the exception would be
+             * thrown would be when a BroadcastReceiver creates a dialog to show the user. */
+            mScreenOffReceiver = null; // clear receiver since it was not used.
+        }
     }
 
     @Override
     protected void onDetachedFromWindow() {
-        getContext().unregisterReceiver(mScreenOffReceiver);
+        if (mScreenOffReceiver != null) {
+            getContext().unregisterReceiver(mScreenOffReceiver);
+            mScreenOffReceiver = null;
+        }
         super.onDetachedFromWindow();
     }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1cd0afe..ce8a224 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -313,6 +313,10 @@
     <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
+    <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" />
+    <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
+    <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
+    <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" />
     <protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
@@ -1346,7 +1350,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" />
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/tests/coretests/src/android/database/PageViewCursorTest.java b/core/tests/coretests/src/android/database/PageViewCursorTest.java
index 62b5410..fba6aaf 100644
--- a/core/tests/coretests/src/android/database/PageViewCursorTest.java
+++ b/core/tests/coretests/src/android/database/PageViewCursorTest.java
@@ -13,20 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.database;
 
+import static com.android.internal.util.ArrayUtils.contains;
+import static com.android.internal.util.Preconditions.checkArgument;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.Nullable;
 import android.content.ContentResolver;
+import android.os.Build;
 import android.os.Bundle;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.util.MathUtils;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.Random;
 
 @RunWith(AndroidJUnit4.class)
@@ -37,32 +45,32 @@
     private static final String NAME_COLUMN = "name";
     private static final String NUM_COLUMN = "num";
 
-    private static final String[] COLUMNS = new String[]{
-      NAME_COLUMN,
-      NUM_COLUMN
+    private static final String[] COLUMNS = new String[] {
+        NAME_COLUMN,
+        NUM_COLUMN
     };
 
     private static final String[] NAMES = new String[] {
-            "000",
-            "111",
-            "222",
-            "333",
-            "444",
-            "555",
-            "666",
-            "777",
-            "888",
-            "999",
-            "aaa",
-            "bbb",
-            "ccc",
-            "ddd",
-            "eee",
-            "fff",
-            "ggg",
-            "hhh",
-            "iii",
-            "jjj"
+        "000",
+        "111",
+        "222",
+        "333",
+        "444",
+        "555",
+        "666",
+        "777",
+        "888",
+        "999",
+        "aaa",
+        "bbb",
+        "ccc",
+        "ddd",
+        "eee",
+        "fff",
+        "ggg",
+        "hhh",
+        "iii",
+        "jjj"
     };
 
     private MatrixCursor mDelegate;
@@ -79,7 +87,7 @@
             row.add(NUM_COLUMN, rand.nextInt());
         }
 
-        mCursor = new PageViewCursor(mDelegate, 10, 5);
+        mCursor = new PageViewCursor(mDelegate, createArgs(10, 5));
     }
 
     @Test
@@ -94,7 +102,7 @@
 
     @Test
     public void testPage_OffsetExceedsCursorCount_EffectivelyEmptyCursor() {
-        mCursor = new PageViewCursor(mDelegate, ITEM_COUNT * 2, 5);
+        mCursor = new PageViewCursor(mDelegate, createArgs(ITEM_COUNT * 2, 5));
         assertEquals(0, mCursor.getCount());
     }
 
@@ -155,13 +163,13 @@
 
     @Test
     public void testCount_ZeroForEmptyCursor() {
-        mCursor = new PageViewCursor(mDelegate, 0, 0);
+        mCursor = new PageViewCursor(mDelegate, createArgs(0, 0));
         assertEquals(0, mCursor.getCount());
     }
 
     @Test
     public void testIsBeforeFirst_TrueForEmptyCursor() {
-        mCursor = new PageViewCursor(mDelegate, 0, 0);
+        mCursor = new PageViewCursor(mDelegate, createArgs(0, 0));
         assertTrue(mCursor.isBeforeFirst());
     }
 
@@ -175,7 +183,7 @@
 
     @Test
     public void testIsAfterLast_TrueForEmptyCursor() {
-        mCursor = new PageViewCursor(mDelegate, 0, 0);
+        mCursor = new PageViewCursor(mDelegate, createArgs(0, 0));
         assertTrue(mCursor.isAfterLast());
     }
 
@@ -247,71 +255,131 @@
     }
 
     @Test
-    public void testOffset_LimitOutOfBounds() {
-        mCursor = new PageViewCursor(mDelegate, 5, 100);
+    public void testLimitOutOfBounds() {
+        mCursor = new PageViewCursor(mDelegate, createArgs(5, 100));
         assertEquals(15, mCursor.getCount());
     }
 
     @Test
-    public void testAutoPagedExtra() {
-        mCursor = new PageViewCursor(mDelegate, 5, 100);
+    public void testOffsetOutOfBounds_EmptyResult() {
+        mCursor = new PageViewCursor(mDelegate, createArgs(100000, 100));
+        assertEquals(0, mCursor.getCount());
+    }
+
+    @Test
+    public void testAddsExtras() {
+        mCursor = new PageViewCursor(mDelegate, createArgs(5, 100));
         assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED));
+        String[] honoredArgs = mCursor.getExtras()
+                .getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
+        assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
+        assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
+    }
+
+    @Test
+    public void testAddsExtras_OnlyOffset() {
+        Bundle args = new Bundle();
+        args.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
+        mCursor = new PageViewCursor(mDelegate, args);
+        String[] honoredArgs = mCursor.getExtras()
+                .getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
+        assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
+        assertFalse(contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
+    }
+
+    @Test
+    public void testAddsExtras_OnlyLimit() {
+        Bundle args = new Bundle();
+        args.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
+        mCursor = new PageViewCursor(mDelegate, args);
+        String[] honoredArgs = mCursor.getExtras()
+                .getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
+        assertFalse(contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
+        assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
     }
 
     @Test
     public void testGetWindow() {
-        mCursor = new PageViewCursor(mDelegate, 5, 5);
+        mCursor = new PageViewCursor(mDelegate, createArgs(5, 5));
         CursorWindow window = mCursor.getWindow();
         assertEquals(5, window.getNumRows());
     }
 
     @Test
-    public void testWrap() {
-        Bundle queryArgs = new Bundle();
-        queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
-        queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
-        Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs);
+    public void testWraps() {
+        Bundle args = createArgs(5, 5);
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
         assertTrue(wrapped instanceof PageViewCursor);
         assertEquals(5, wrapped.getCount());
     }
 
     @Test
-    public void testWrap_NoOpWithoutPagingArgs() {
+    public void testWraps_NullExtras() {
+        Bundle args = createArgs(5, 5);
+        mDelegate.setExtras(null);
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
+        assertTrue(wrapped instanceof PageViewCursor);
+        assertEquals(5, wrapped.getCount());
+    }
+
+    @Test
+    public void testWraps_WithJustOffset() {
+        Bundle args = new Bundle();
+        args.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
+        assertTrue(wrapped instanceof PageViewCursor);
+        assertEquals(15, wrapped.getCount());
+    }
+
+    @Test
+    public void testWraps_WithJustLimit() {
+        Bundle args = new Bundle();
+        args.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
+        assertTrue(wrapped instanceof PageViewCursor);
+        assertEquals(5, wrapped.getCount());
+    }
+
+    @Test
+    public void testNoWrap_WithoutPagingArgs() {
         Cursor wrapped = PageViewCursor.wrap(mDelegate, Bundle.EMPTY);
         assertTrue(mDelegate == wrapped);
     }
 
     @Test
-    public void testWrap_NoOpCursorsWithExistingPaging_ByTotalSize() {
+    public void testNoWrap_CursorsHasExistingPaging_ByTotalSize() {
         Bundle extras = new Bundle();
         extras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, 5);
         mDelegate.setExtras(extras);
 
-        Bundle queryArgs = new Bundle();
-        queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
-        queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
-        Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs);
+        Bundle args = createArgs(5, 5);
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
         assertTrue(mDelegate == wrapped);
     }
 
     @Test
-    public void testWrap_NoOpCursorsWithExistingPaging_ByHonoredArgs() {
+    public void testNoWrap_CursorsHasExistingPaging_ByHonoredArgs() {
         Bundle extras = new Bundle();
         extras.putStringArray(
                 ContentResolver.EXTRA_HONORED_ARGS,
                 new String[] {
-                    ContentResolver.QUERY_ARG_OFFSET,
-                    ContentResolver.QUERY_ARG_LIMIT
+                        ContentResolver.QUERY_ARG_OFFSET,
+                        ContentResolver.QUERY_ARG_LIMIT
                 });
         mDelegate.setExtras(extras);
 
-        Bundle queryArgs = new Bundle();
-        queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
-        queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
-        Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs);
+        Bundle args = createArgs(5, 5);
+        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
         assertTrue(mDelegate == wrapped);
     }
 
+    private static Bundle createArgs(int offset, int limit) {
+        Bundle args = new Bundle();
+        args.putInt(ContentResolver.QUERY_ARG_OFFSET, offset);
+        args.putInt(ContentResolver.QUERY_ARG_LIMIT, limit);
+        return args;
+    }
+
     private void assertStringAt(int row, int column, String expected) {
         mCursor.moveToPosition(row);
         assertEquals(expected, mCursor.getString(column));
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 3fbf169..108585d 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -349,7 +349,6 @@
         assertCanBeHandled(new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIFI_IP_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SETTINGS));
-        assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SAVED_NETWORK_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
 
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
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/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index e32fd63..1450ec9 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -98,6 +98,7 @@
 }
 
 void TaskQueue::queueAtFront(RenderTask* task) {
+    LOG_ALWAYS_FATAL_IF(task->mNext || mHead == task, "Task is already in the queue!");
     if (mTail) {
         task->mNext = mHead;
         mHead = task;
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/UnsupportedCasException.java b/media/java/android/media/UnsupportedCasException.java
deleted file mode 100644
index 3167637..0000000
--- a/media/java/android/media/UnsupportedCasException.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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;
-
-/**
- * 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 final class UnsupportedCasException extends MediaCasException {
-    public UnsupportedCasException(String detailMessage) {
-        super(detailMessage);
-    }
-}
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/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java
index 957c582..762f0c0 100644
--- a/media/java/android/media/tv/TvInputHardwareInfo.java
+++ b/media/java/android/media/tv/TvInputHardwareInfo.java
@@ -20,6 +20,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
+import android.hardware.tv.input.V1_0.Constants;
 import android.media.AudioManager;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -37,16 +38,16 @@
     static final String TAG = "TvInputHardwareInfo";
 
     // Match hardware/libhardware/include/hardware/tv_input.h
-    public static final int TV_INPUT_TYPE_OTHER_HARDWARE = 1;
-    public static final int TV_INPUT_TYPE_TUNER          = 2;
-    public static final int TV_INPUT_TYPE_COMPOSITE      = 3;
-    public static final int TV_INPUT_TYPE_SVIDEO         = 4;
-    public static final int TV_INPUT_TYPE_SCART          = 5;
-    public static final int TV_INPUT_TYPE_COMPONENT      = 6;
-    public static final int TV_INPUT_TYPE_VGA            = 7;
-    public static final int TV_INPUT_TYPE_DVI            = 8;
-    public static final int TV_INPUT_TYPE_HDMI           = 9;
-    public static final int TV_INPUT_TYPE_DISPLAY_PORT   = 10;
+    public static final int TV_INPUT_TYPE_OTHER_HARDWARE = Constants.TV_INPUT_TYPE_OTHER;
+    public static final int TV_INPUT_TYPE_TUNER          = Constants.TV_INPUT_TYPE_TUNER;
+    public static final int TV_INPUT_TYPE_COMPOSITE      = Constants.TV_INPUT_TYPE_COMPOSITE;
+    public static final int TV_INPUT_TYPE_SVIDEO         = Constants.TV_INPUT_TYPE_SVIDEO;
+    public static final int TV_INPUT_TYPE_SCART          = Constants.TV_INPUT_TYPE_SCART;
+    public static final int TV_INPUT_TYPE_COMPONENT      = Constants.TV_INPUT_TYPE_COMPONENT;
+    public static final int TV_INPUT_TYPE_VGA            = Constants.TV_INPUT_TYPE_VGA;
+    public static final int TV_INPUT_TYPE_DVI            = Constants.TV_INPUT_TYPE_DVI;
+    public static final int TV_INPUT_TYPE_HDMI           = Constants.TV_INPUT_TYPE_HDMI;
+    public static final int TV_INPUT_TYPE_DISPLAY_PORT   = Constants.TV_INPUT_TYPE_DISPLAY_PORT;
 
     /** @hide */
     @Retention(SOURCE)
@@ -58,17 +59,20 @@
     /**
      * The hardware is unsure about the connection status or does not support cable detection.
      */
-    public static final int CABLE_CONNECTION_STATUS_UNKNOWN = 0;
+    public static final int CABLE_CONNECTION_STATUS_UNKNOWN =
+            Constants.CABLE_CONNECTION_STATUS_UNKNOWN;
 
     /**
      * Cable is connected to the hardware.
      */
-    public static final int CABLE_CONNECTION_STATUS_CONNECTED = 1;
+    public static final int CABLE_CONNECTION_STATUS_CONNECTED =
+            Constants.CABLE_CONNECTION_STATUS_CONNECTED;
 
     /**
      * Cable is disconnected to the hardware.
      */
-    public static final int CABLE_CONNECTION_STATUS_DISCONNECTED = 2;
+    public static final int CABLE_CONNECTION_STATUS_DISCONNECTED =
+            Constants.CABLE_CONNECTION_STATUS_DISCONNECTED;
 
     public static final Parcelable.Creator<TvInputHardwareInfo> CREATOR =
             new Parcelable.Creator<TvInputHardwareInfo>() {
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/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/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 0280f26..1f86f8b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -526,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/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 14bb02d..a8629f8 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -51,7 +51,7 @@
     <bool name="def_wifi_on">false</bool>
     <!-- 0 == never, 1 == only when plugged in, 2 == always -->
     <integer name="def_wifi_sleep_policy">2</integer>
-    <bool name="def_wifi_wakeup_enabled">false</bool>
+    <bool name="def_wifi_wakeup_enabled">true</bool>
     <bool name="def_networks_available_notification_on">true</bool>
 
     <bool name="def_backup_enabled">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1a752f9..0d0ddf2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2798,7 +2798,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 142;
+            private static final int SETTINGS_VERSION = 143;
 
             private final int mUserId;
 
@@ -3343,10 +3343,30 @@
                     currentVersion = 142;
                 }
 
+                if (currentVersion == 142) {
+                    // Version 142: Set a default value for Wi-Fi wakeup feature.
+                    if (userId == UserHandle.USER_SYSTEM) {
+                        final SettingsState globalSettings = getGlobalSettingsLocked();
+                        Setting currentSetting = globalSettings.getSettingLocked(
+                                Settings.Global.WIFI_WAKEUP_ENABLED);
+                        if (currentSetting.isNull()) {
+                            globalSettings.insertSettingLocked(
+                                    Settings.Global.WIFI_WAKEUP_ENABLED,
+                                    getContext().getResources().getBoolean(
+                                            R.bool.def_wifi_wakeup_enabled) ? "1" : "0",
+                                    null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                        }
+                    }
+
+                    currentVersion = 143;
+                }
+
                 if (currentVersion != newVersion) {
                     Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
                             + newVersion + " left it at "
-                            + currentVersion + " instead; this is probably a bug", new Throwable());
+                            + currentVersion +
+                            " instead; this is probably a bug. Did you update SETTINGS_VERSION?",
+                            new Throwable());
                     if (DEBUG) {
                         throw new RuntimeException("db upgrade error");
                     }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 6bfab78..2519e02 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -113,6 +113,7 @@
     <uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+    <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
     <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <uses-permission android:name="android.permission.MANAGE_AUTO_FILL" />
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 1ebfbad..635c96f 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -38,7 +38,6 @@
     android-support-v17-leanback
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    framework-protos \
     SystemUI-tags \
     SystemUI-proto
 
diff --git a/packages/SystemUI/plugin/Android.mk b/packages/SystemUI/plugin/Android.mk
index 05ee6b2..e22dddb 100644
--- a/packages/SystemUI/plugin/Android.mk
+++ b/packages/SystemUI/plugin/Android.mk
@@ -36,4 +36,6 @@
 
 LOCAL_JAVA_LIBRARIES := SystemUIPluginLib
 
+LOCAL_PROGUARD_ENABLED := disabled
+
 include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5b20716..bf17e38 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1878,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/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index b689a850..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;
@@ -169,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);
@@ -189,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();
@@ -368,6 +392,9 @@
      * Sets the minimized state.
      */
     void setMinimizedStateInternal(boolean isMinimized) {
+        if (!mEnableMinimize) {
+            return;
+        }
         setMinimizedState(isMinimized, false /* fromController */);
     }
 
@@ -375,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);
@@ -483,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) {
@@ -550,10 +580,10 @@
                 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 onLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
-                final boolean isFlingToBot = !isHorizontal && mMovementWithinDismiss && vel.y > 0;
-                final boolean isFlingToEdge = isHorizontal && mMovementWithinMinimize
-                        && (onLeft ? vel.x < 0 : vel.x > 0);
+                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)) {
@@ -563,7 +593,8 @@
                             MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
                             METRIC_VALUE_DISMISSED_BY_DRAG);
                     return true;
-                } else if (!mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) {
+                } else if (mEnableMinimize &&
+                        !mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) {
                     // Pip should be minimized
                     setMinimizedStateInternal(true);
                     if (mMenuController.isMenuVisible()) {
@@ -631,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/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/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index bfe55bc..5370ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -987,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)));
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/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4d78350..3d1c251 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -19,15 +19,10 @@
 import static android.service.autofill.AutofillService.EXTRA_ACTIVITY_TOKEN;
 import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
 import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
-import static android.view.autofill.AutofillManager.FLAG_VIEW_ENTERED;
-import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED;
 import static android.view.autofill.AutofillManager.FLAG_START_SESSION;
-import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED;
-import static android.view.autofill.AutofillManager.FLAG_MANUAL_REQUEST;
 
 import static com.android.server.autofill.Helper.DEBUG;
 import static com.android.server.autofill.Helper.VERBOSE;
-import static com.android.server.autofill.Helper.findValue;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,32 +30,23 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.app.assist.AssistStructure.WindowNode;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.graphics.Rect;
-import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Parcelable;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.service.autofill.AutofillService;
 import android.service.autofill.AutofillServiceInfo;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
 import android.service.autofill.IAutoFillService;
-import android.service.autofill.SaveInfo;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.LocalLog;
@@ -68,21 +54,16 @@
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
-import android.view.autofill.IAutofillWindowPresenter;
+
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.IResultReceiver;
 import com.android.server.autofill.ui.AutoFillUI;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Map;
-import java.util.Map.Entry;
 
 /**
  * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
@@ -93,13 +74,12 @@
 
     private static final String TAG = "AutofillManagerServiceImpl";
 
-    private static final int MSG_SERVICE_SAVE = 1;
+    static final int MSG_SERVICE_SAVE = 1;
 
     private final int mUserId;
     private final Context mContext;
     private final Object mLock;
     private final AutoFillUI mUi;
-    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     private RemoteCallbackList<IAutoFillManagerClient> mClients;
     private AutofillServiceInfo mInfo;
@@ -358,8 +338,9 @@
     private Session createSessionByTokenLocked(@NonNull IBinder activityToken,
             @Nullable IBinder windowToken, @NonNull IBinder appCallbackToken, boolean hasCallback,
             int flags, @NonNull String packageName) {
-        final Session newSession = new Session(mContext, activityToken,
-                windowToken, appCallbackToken, hasCallback, flags, packageName);
+        final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
+                activityToken, windowToken, appCallbackToken, hasCallback, flags,
+                mInfo.getServiceInfo().getComponentName(), packageName);
         mSessions.put(activityToken, newSession);
 
         /*
@@ -400,6 +381,10 @@
         session.updateLocked(autofillId, virtualBounds, value, flags);
     }
 
+    void removeSessionLocked(IBinder activityToken) {
+        mSessions.remove(activityToken);
+    }
+
     private void handleSessionSave(IBinder activityToken) {
         synchronized (mLock) {
             final Session session = mSessions.get(activityToken);
@@ -423,6 +408,25 @@
         mSessions.clear();
     }
 
+    void disableSelf() {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final String autoFillService = Settings.Secure.getStringForUser(
+                    mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
+            if (mInfo.getServiceInfo().getComponentName().equals(
+                    ComponentName.unflattenFromString(autoFillService))) {
+                Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    CharSequence getServiceLabel() {
+        return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager());
+    }
+
     void dumpLocked(String prefix, PrintWriter pw) {
         final String prefix2 = prefix + "  ";
 
@@ -495,804 +499,4 @@
                 + ", component=" + (mInfo != null
                 ? mInfo.getServiceInfo().getComponentName() : null) + "]";
     }
-
-    /**
-     * State for a given view with a AutofillId.
-     *
-     * <p>This class holds state about a view and calls its listener when the fill UI is ready to
-     * be displayed for the view.
-     */
-    static final class ViewState {
-        interface Listener {
-            /**
-             * Called when the fill UI is ready to be shown for this view.
-             */
-            void onFillReady(FillResponse fillResponse, AutofillId focusedId,
-                    @Nullable AutofillValue value);
-        }
-
-        final AutofillId mId;
-        private final Listener mListener;
-        // TODO(b/33197203): would not need a reference to response and session if it was an inner
-        // class of Session...
-        private final Session mSession;
-        // TODO(b/33197203): encapsulate access so it's not called by UI
-        FillResponse mResponse;
-        Intent mAuthIntent;
-
-        private AutofillValue mAutofillValue;
-
-        // Bounds if a virtual view, null otherwise
-        private Rect mVirtualBounds;
-
-        private boolean mValueUpdated;
-
-        ViewState(Session session, AutofillId id, Listener listener) {
-            mSession = session;
-            mId = id;
-            mListener = listener;
-        }
-
-        /**
-         * Response should only be set once.
-         */
-        void setResponse(FillResponse response) {
-            mResponse = response;
-            maybeCallOnFillReady();
-        }
-
-        /**
-         * Used when a {@link FillResponse} requires authentication to be unlocked.
-         */
-        void setResponse(FillResponse response, Intent authIntent) {
-            mAuthIntent = authIntent;
-            setResponse(response);
-        }
-
-        CharSequence getServiceName() {
-            return mSession.getServiceName();
-        }
-
-        // 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 virtualBounds) {
-            if (autofillValue != null) {
-                mAutofillValue = autofillValue;
-            }
-            if (virtualBounds != null) {
-                mVirtualBounds = virtualBounds;
-            }
-
-            maybeCallOnFillReady();
-        }
-
-        /**
-         * Calls {@link
-         * 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)) {
-                mListener.onFillReady(mResponse, mId, mAutofillValue);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds
-                    + ", updated = " + mValueUpdated + "]";
-        }
-
-        void dump(String prefix, PrintWriter pw) {
-            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("virtualBounds:" ); pw.println(mVirtualBounds);
-            pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
-        }
-    }
-
-    /**
-     * A session for a given activity.
-     *
-     * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
-     * of the current {@link ViewState} to display the appropriate UI.
-     *
-     * <p>Although the autofill requests and callbacks are stateless from the service's point of
-     * view, we need to keep state in the framework side for cases such as authentication. For
-     * example, when service return a {@link FillResponse} that contains all the fields needed
-     * to fill the activity but it requires authentication first, that response need to be held
-     * until the user authenticates or it times out.
-     */
-    // TODO(b/33197203): make sure sessions are removed (and tested by CTS):
-    // - On all authentication scenarios.
-    // - When user does not interact back after a while.
-    // - When service is unbound.
-    final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
-            AutoFillUI.AutoFillUiCallback {
-        private final IBinder mActivityToken;
-        private final IBinder mWindowToken;
-
-        /** Package name of the app that is auto-filled */
-        @NonNull private final String mPackageName;
-
-        @GuardedBy("mLock")
-        private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>();
-
-        @GuardedBy("mLock")
-        @Nullable
-        private ViewState mCurrentViewState;
-
-        private final IAutoFillManagerClient mClient;
-
-        @GuardedBy("mLock")
-        RemoteFillService mRemoteFillService;
-
-        // TODO(b/33197203): Get a response per view instead of per activity.
-        @GuardedBy("mLock")
-        private FillResponse mCurrentResponse;
-
-        /**
-         * Used to remember which {@link Dataset} filled the session.
-         */
-        // TODO(b/33197203): might need more than one once we support partitions
-        @GuardedBy("mLock")
-        private Dataset mAutoFilledDataset;
-
-        /**
-         * Assist structure sent by the app; it will be updated (sanitized, change values for save)
-         * before sent to {@link AutofillService}.
-         */
-        @GuardedBy("mLock")
-        private AssistStructure mStructure;
-
-        /**
-         * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
-         */
-        private boolean mHasCallback;
-
-        /**
-         * Flags used to start the session.
-         */
-        private int mFlags;
-        private Session(@NonNull Context context, @NonNull IBinder activityToken,
-                @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback,
-                int flags, @NonNull String packageName) {
-            mRemoteFillService = new RemoteFillService(context,
-                    mInfo.getServiceInfo().getComponentName(), mUserId, this);
-            mActivityToken = activityToken;
-            mWindowToken = windowToken;
-            mHasCallback = hasCallback;
-            mFlags = flags;
-            mPackageName = packageName;
-
-            mClient = IAutoFillManagerClient.Stub.asInterface(client);
-            try {
-                client.linkToDeath(() -> {
-                    if (DEBUG) {
-                        Slog.d(TAG, "app binder died");
-                    }
-
-                    removeSelf();
-                }, 0);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "linkToDeath() on mClient failed: " + e);
-            }
-
-            mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
-        }
-
-        // FillServiceCallbacks
-        @Override
-        public void onFillRequestSuccess(@Nullable FillResponse response,
-                @NonNull String servicePackageName) {
-            if (response == null) {
-                // Nothing to be done, but need to notify client.
-                notifyUnavailableToClient();
-                removeSelf();
-                return;
-            }
-
-            if ((response.getDatasets() == null || response.getDatasets().isEmpty())
-                            && response.getAuthentication() == null) {
-                // Response is "empty" from an UI point of view, need to notify client.
-                notifyUnavailableToClient();
-            }
-            synchronized (mLock) {
-                processResponseLocked(response);
-            }
-
-            LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_REQUEST))
-                    .setType(MetricsProto.MetricsEvent.TYPE_SUCCESS)
-                    .setPackageName(mPackageName)
-                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
-                            response.getDatasets() == null ? 0 : response.getDatasets().size())
-                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
-                            servicePackageName);
-            mMetricsLogger.write(log);
-        }
-
-        // FillServiceCallbacks
-        @Override
-        public void onFillRequestFailure(@Nullable CharSequence message,
-                @NonNull String servicePackageName) {
-            LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_REQUEST))
-                    .setType(MetricsProto.MetricsEvent.TYPE_FAILURE)
-                    .setPackageName(mPackageName)
-                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
-                            servicePackageName);
-            mMetricsLogger.write(log);
-
-            getUiForShowing().showError(message);
-            removeSelf();
-        }
-
-        // FillServiceCallbacks
-        @Override
-        public void onSaveRequestSuccess(@NonNull String servicePackageName) {
-            LogMaker log = (new LogMaker(
-                    MetricsProto.MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
-                    .setType(MetricsProto.MetricsEvent.TYPE_SUCCESS)
-                    .setPackageName(mPackageName)
-                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
-                            servicePackageName);
-            mMetricsLogger.write(log);
-
-            // Nothing left to do...
-            removeSelf();
-        }
-
-        // FillServiceCallbacks
-        @Override
-        public void onSaveRequestFailure(@Nullable CharSequence message,
-                @NonNull String servicePackageName) {
-            LogMaker log = (new LogMaker(
-                    MetricsProto.MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
-                    .setType(MetricsProto.MetricsEvent.TYPE_FAILURE)
-                    .setPackageName(mPackageName)
-                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
-                            servicePackageName);
-            mMetricsLogger.write(log);
-
-            getUiForShowing().showError(message);
-            removeSelf();
-        }
-
-        // FillServiceCallbacks
-        @Override
-        public void authenticate(IntentSender intent) {
-            final Intent fillInIntent;
-            synchronized (mLock) {
-                fillInIntent = createAuthFillInIntent(mStructure);
-            }
-            mHandlerCaller.getHandler().post(() -> startAuthentication(intent, fillInIntent));
-        }
-
-        // FillServiceCallbacks
-        @Override
-        public void onDisableSelf() {
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                final String autoFillService = Settings.Secure.getStringForUser(
-                        mContext.getContentResolver(),
-                        Settings.Secure.AUTOFILL_SERVICE, mUserId);
-                if (mInfo.getServiceInfo().getComponentName().equals(
-                        ComponentName.unflattenFromString(autoFillService))) {
-                    Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                            Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-            synchronized (mLock) {
-                removeSelfLocked();
-            }
-        }
-
-        // FillServiceCallbacks
-        @Override
-        public void onServiceDied(RemoteFillService service) {
-            // TODO(b/33197203): implement
-        }
-
-        // AutoFillUiCallback
-        @Override
-        public void fill(Dataset dataset) {
-            mHandlerCaller.getHandler().post(() -> autoFill(dataset));
-        }
-
-        // AutoFillUiCallback
-        @Override
-        public void save() {
-            mHandlerCaller.getHandler().obtainMessage(MSG_SERVICE_SAVE, mActivityToken)
-                    .sendToTarget();
-        }
-
-        // AutoFillUiCallback
-        @Override
-        public void cancelSave() {
-            mHandlerCaller.getHandler().post(() -> removeSelf());
-        }
-
-        // AutoFillUiCallback
-        @Override
-        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) {
-            if (mCurrentResponse == null || data == null) {
-                removeSelf();
-            } else {
-                Parcelable result = data.getParcelable(
-                        AutofillManager.EXTRA_AUTHENTICATION_RESULT);
-                if (result instanceof FillResponse) {
-                    mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_AUTHENTICATED,
-                            mPackageName);
-
-                    mCurrentResponse = (FillResponse) result;
-                    processResponseLocked(mCurrentResponse);
-                } else if (result instanceof Dataset) {
-                    Dataset dataset = (Dataset) result;
-                    final int index = mCurrentResponse.getDatasets().indexOf(mAutoFilledDataset);
-                    if (index >= 0) {
-                        mCurrentResponse.getDatasets().set(index, dataset);
-                        autoFill(dataset);
-                    }
-                }
-            }
-        }
-
-        public void setHasCallback(boolean hasIt) {
-            mHasCallback = hasIt;
-        }
-
-        /**
-         * Shows the save UI, when session can be saved.
-         *
-         * @return {@code true} if session is done, or {@code false} if it's pending user action.
-         */
-        public boolean showSaveLocked() {
-            if (mStructure == null) {
-                Slog.wtf(TAG, "showSaveLocked(): no mStructure");
-                return true;
-            }
-            if (mCurrentResponse == null) {
-                // Happens when the activity / session was finished before the service replied, or
-                // when the service cannot autofill it (and returned a null response).
-                if (DEBUG) {
-                    Slog.d(TAG, "showSaveLocked(): no mCurrentResponse");
-                }
-                return true;
-            }
-            final SaveInfo saveInfo = mCurrentResponse.getSaveInfo();
-            if (DEBUG) {
-                Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
-            }
-
-            /*
-             * The Save dialog is only shown if all conditions below are met:
-             *
-             * - saveInfo is not null
-             * - autofillValue of all required ids is not null
-             * - autofillValue of at least one id (required or optional) has changed.
-             */
-
-            if (saveInfo == null) {
-                return true;
-            }
-
-            final AutofillId[] requiredIds = saveInfo.getRequiredIds();
-            if (requiredIds == null || requiredIds.length == 0) {
-                Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo");
-                return true;
-            }
-
-            boolean allRequiredAreNotEmpty = true;
-            boolean atLeastOneChanged = false;
-            for (int i = 0; i < requiredIds.length; i++) {
-                final AutofillId id = requiredIds[i];
-                final ViewState state = mViewStates.get(id);
-                if (state == null || state.mAutofillValue == null
-                         || state.mAutofillValue.isEmpty()) {
-                    final ViewNode node = findViewNodeByIdLocked(id);
-                    if (node == null) {
-                        Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id);
-                        allRequiredAreNotEmpty = false;
-                        break;
-                    }
-                    final AutofillValue initialValue = node.getAutofillValue();
-                    if (initialValue == null || initialValue.isEmpty()) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "finishSessionLocked(): empty initial value for " + id );
-                        }
-                        allRequiredAreNotEmpty = false;
-                        break;
-                    }
-                }
-                if (state.mValueUpdated) {
-                    final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
-                    if (!state.mAutofillValue.equals(filledValue)) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
-                                    + filledValue + " => " + state.mAutofillValue);
-                        }
-                        atLeastOneChanged = true;
-                    }
-                } else {
-                    if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": "
-                                    + state.mAutofillValue);
-                        }
-                        allRequiredAreNotEmpty = false;
-                        break;
-
-                    }
-                }
-            }
-
-            if (allRequiredAreNotEmpty) {
-                if (!atLeastOneChanged && saveInfo.getOptionalIds() != null) {
-                    for (int i = 0; i < saveInfo.getOptionalIds().length; i++) {
-                        final AutofillId id = saveInfo.getOptionalIds()[i];
-                        final ViewState state = mViewStates.get(id);
-                        if (state != null && state.mAutofillValue != null && state.mValueUpdated) {
-                            final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
-                            if (!state.mAutofillValue.equals(filledValue)) {
-                                if (DEBUG) {
-                                    Slog.d(TAG, "finishSessionLocked(): found a change on optional "
-                                            + id + ": " + filledValue + " => "
-                                            + state.mAutofillValue);
-                                }
-                                atLeastOneChanged = true;
-                                break;
-                            }
-                        }
-                    }
-                }
-                if (atLeastOneChanged) {
-                    getUiForShowing().showSaveUi(
-                            mInfo.getServiceInfo().loadLabel(mContext.getPackageManager()),
-                            saveInfo, mPackageName);
-                    return false;
-                }
-            }
-            // Nothing changed...
-            if (DEBUG) {
-                Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
-                        + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
-                        + ", atLeastOneChanged=" + atLeastOneChanged);
-            }
-            return true;
-        }
-
-        /**
-         * Calls service when user requested save.
-         */
-        private void callSaveLocked() {
-            if (DEBUG) {
-                Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
-            }
-
-            final Bundle extras = this.mCurrentResponse.getExtras();
-
-            for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
-                final AutofillValue value = entry.getValue().mAutofillValue;
-                if (value == null) {
-                    if (VERBOSE) {
-                        Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey());
-                    }
-                    continue;
-                }
-                final AutofillId id = entry.getKey();
-                final ViewNode node = findViewNodeByIdLocked(id);
-                if (node == null) {
-                    Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
-                    continue;
-                }
-                if (VERBOSE) {
-                    Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
-                }
-
-                node.updateAutofillValue(value);
-            }
-
-            // Sanitize structure before it's sent to service.
-            mStructure.sanitizeForParceling(false);
-
-            if (VERBOSE) {
-                Slog.v(TAG, "Dumping " + mStructure + " before calling service.save()");
-                mStructure.dump();
-            }
-
-            mRemoteFillService.onSaveRequest(mStructure, extras);
-        }
-
-        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");
-                return;
-            }
-
-            ViewState viewState = mViewStates.get(id);
-            if (viewState == null) {
-                viewState = new ViewState(this, id, this);
-                mViewStates.put(id, viewState);
-            }
-
-            if ((flags & FLAG_START_SESSION) != 0) {
-                // View is triggering autofill.
-                mCurrentViewState = viewState;
-                viewState.update(value, virtualBounds);
-                return;
-            }
-
-            if ((flags & FLAG_VALUE_CHANGED) != 0) {
-                if (value != null && !value.equals(viewState.mAutofillValue)) {
-                    viewState.mValueUpdated = true;
-
-                    // Must check if this update was caused by autofilling the view, in which
-                    // case we just update the value, but not the UI.
-                    if (mAutoFilledDataset != null) {
-                        final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
-                        if (value.equals(filledValue)) {
-                            viewState.mAutofillValue = value;
-                            return;
-                        }
-                    }
-
-                    // Change value
-                    viewState.mAutofillValue = value;
-
-                    // Update the chooser UI
-                    if (value.isText()) {
-                        getUiForShowing().filterFillUi(value.getTextValue().toString());
-                    } else {
-                        getUiForShowing().filterFillUi(null);
-                    }
-                }
-
-                return;
-            }
-
-            if ((flags & FLAG_VIEW_ENTERED) != 0) {
-                // Remove the UI if the ViewState has changed.
-                if (mCurrentViewState != viewState) {
-                    mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null);
-                    mCurrentViewState = viewState;
-                }
-
-                // If the ViewState is ready to be displayed, onReady() will be called.
-                viewState.update(value, virtualBounds);
-
-                // TODO(b/33197203): Remove when there is a response per activity.
-                if (mCurrentResponse != null) {
-                    viewState.setResponse(mCurrentResponse);
-                }
-
-                return;
-            }
-
-            if ((flags & FLAG_VIEW_EXITED) != 0) {
-                if (mCurrentViewState == viewState) {
-                    mUi.hideFillUi(viewState.mId);
-                    mCurrentViewState = null;
-                }
-                return;
-            }
-
-            Slog.w(TAG, "updateLocked(): unknown flags " + flags);
-        }
-
-        @Override
-        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, filterText, mPackageName);
-        }
-
-        private void notifyUnavailableToClient() {
-            if (mCurrentViewState == null) {
-                // TODO(b/33197203): temporary sanity check; should never happen
-                Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null");
-                return;
-            }
-            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) {
-            if (DEBUG) {
-                Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication()
-                    + "):" + response);
-            }
-
-            if (mCurrentViewState == null) {
-                // TODO(b/33197203): temporary sanity check; should never happen
-                Slog.w(TAG, "processResponseLocked(): mCurrentViewState is null");
-                return;
-            }
-
-            mCurrentResponse = response;
-
-            if (mCurrentResponse.getAuthentication() != null) {
-                // Handle authentication.
-                final Intent fillInIntent = createAuthFillInIntent(mStructure);
-                mCurrentViewState.setResponse(mCurrentResponse, fillInIntent);
-                return;
-            }
-
-            if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null
-                    && response.getDatasets().size() == 1) {
-                Slog.d(TAG, "autofilling manual request directly");
-                autoFill(response.getDatasets().get(0));
-                return;
-            }
-
-            mCurrentViewState.setResponse(mCurrentResponse);
-        }
-
-        void autoFill(Dataset dataset) {
-            synchronized (mLock) {
-                mAutoFilledDataset = dataset;
-
-                // Autofill it directly...
-                if (dataset.getAuthentication() == null) {
-                    autoFillApp(dataset);
-                    return;
-                }
-
-                // ...or handle authentication.
-                Intent fillInIntent = createAuthFillInIntent(mStructure);
-                startAuthentication(dataset.getAuthentication(), fillInIntent);
-            }
-        }
-
-        CharSequence getServiceName() {
-            return AutofillManagerServiceImpl.this.getServiceName();
-        }
-
-        private Intent createAuthFillInIntent(AssistStructure structure) {
-            Intent fillInIntent = new Intent();
-            fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure);
-            return fillInIntent;
-        }
-
-        private void startAuthentication(IntentSender intent, Intent fillInIntent) {
-            try {
-                mClient.authenticate(intent, fillInIntent);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Error launching auth intent", e);
-            }
-        }
-
-        void dumpLocked(String prefix, PrintWriter pw) {
-            pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
-            pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags);
-            pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
-            pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
-            pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState);
-            pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size());
-            final String prefix2 = prefix + "  ";
-            for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
-                pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
-                entry.getValue().dump(prefix2, pw);
-            }
-            if (VERBOSE) {
-                pw.print(prefix); pw.print("mStructure: " );
-                // TODO(b/33197203): add method do dump AssistStructure on pw
-                if (mStructure != null) {
-                    pw.println("look at logcat" );
-                    mStructure.dump(); // dumps to logcat
-                } else {
-                    pw.println("null");
-                }
-            }
-            pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
-            mRemoteFillService.dump(prefix, pw);
-        }
-
-        void autoFillApp(Dataset dataset) {
-            synchronized (mLock) {
-                try {
-                    if (DEBUG) {
-                        Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
-                    }
-                    mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues());
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Error autofilling activity: " + e);
-                }
-            }
-        }
-
-        private AutoFillUI getUiForShowing() {
-            synchronized (mLock) {
-                mUi.setCallback(this);
-                return mUi;
-            }
-        }
-
-        private ViewNode findViewNodeByIdLocked(AutofillId id) {
-            final int size = mStructure.getWindowNodeCount();
-            for (int i = 0; i < size; i++) {
-                final WindowNode window = mStructure.getWindowNodeAt(i);
-                final ViewNode root = window.getRootViewNode();
-                if (id.equals(root.getAutofillId())) {
-                    return root;
-                }
-                final ViewNode child = findViewNodeByIdLocked(root, id);
-                if (child != null) {
-                    return child;
-                }
-            }
-            return null;
-        }
-
-        private ViewNode findViewNodeByIdLocked(ViewNode parent, AutofillId id) {
-            final int childrenSize = parent.getChildCount();
-            if (childrenSize > 0) {
-                for (int i = 0; i < childrenSize; i++) {
-                    final ViewNode child = parent.getChildAt(i);
-                    if (id.equals(child.getAutofillId())) {
-                        return child;
-                    }
-                    final ViewNode grandChild = findViewNodeByIdLocked(child, id);
-                    if (grandChild != null && id.equals(grandChild.getAutofillId())) {
-                        return grandChild;
-                    }
-                }
-            }
-            return null;
-        }
-
-        private void destroyLocked() {
-            mRemoteFillService.destroy();
-            mUi.setCallback(null);
-            mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_FINISHED,
-                    mPackageName);
-        }
-
-        void removeSelf() {
-            synchronized (mLock) {
-                removeSelfLocked();
-            }
-        }
-
-        private void removeSelfLocked() {
-            if (VERBOSE) {
-                Slog.v(TAG, "removeSelfLocked()");
-            }
-            destroyLocked();
-            mSessions.remove(mActivityToken);
-        }
-    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
new file mode 100644
index 0000000..1093e9e
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -0,0 +1,764 @@
+/*
+ * 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.autofill;
+
+import static android.view.autofill.AutofillManager.FLAG_MANUAL_REQUEST;
+import static android.view.autofill.AutofillManager.FLAG_START_SESSION;
+import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED;
+import static android.view.autofill.AutofillManager.FLAG_VIEW_ENTERED;
+import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED;
+
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.VERBOSE;
+import static com.android.server.autofill.Helper.findValue;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.graphics.Rect;
+import android.metrics.LogMaker;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutoFillManagerClient;
+import android.view.autofill.IAutofillWindowPresenter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.HandlerCaller;
+import com.android.server.autofill.ui.AutoFillUI;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A session for a given activity.
+ *
+ * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
+ * of the current {@link ViewState} to display the appropriate UI.
+ *
+ * <p>Although the autofill requests and callbacks are stateless from the service's point of
+ * view, we need to keep state in the framework side for cases such as authentication. For
+ * example, when service return a {@link FillResponse} that contains all the fields needed
+ * to fill the activity but it requires authentication first, that response need to be held
+ * until the user authenticates or it times out.
+ */
+// TODO(b/33197203): make sure sessions are removed (and tested by CTS):
+// - On all authentication scenarios.
+// - When user does not interact back after a while.
+// - When service is unbound.
+final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
+        AutoFillUI.AutoFillUiCallback {
+    private static final String TAG = "AutofillSession";
+
+    private final AutofillManagerServiceImpl mService;
+    private final IBinder mActivityToken;
+    private final IBinder mWindowToken;
+    private final HandlerCaller mHandlerCaller;
+    private final Object mLock;
+    private final AutoFillUI mUi;
+
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
+    /** Package name of the app that is auto-filled */
+    @NonNull private final String mPackageName;
+
+    @GuardedBy("mLock")
+    private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    @Nullable
+    private ViewState mCurrentViewState;
+
+    private final IAutoFillManagerClient mClient;
+
+    @GuardedBy("mLock")
+    RemoteFillService mRemoteFillService;
+
+    // TODO(b/33197203): Get a response per view instead of per activity.
+    @GuardedBy("mLock")
+    private FillResponse mCurrentResponse;
+
+    /**
+     * Used to remember which {@link Dataset} filled the session.
+     */
+    // TODO(b/33197203): might need more than one once we support partitions
+    @GuardedBy("mLock")
+    private Dataset mAutoFilledDataset;
+
+    /**
+     * Assist structure sent by the app; it will be updated (sanitized, change values for save)
+     * before sent to {@link AutofillService}.
+     */
+    @GuardedBy("mLock") AssistStructure mStructure;
+
+    /**
+     * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
+     */
+    private boolean mHasCallback;
+
+    /**
+     * Flags used to start the session.
+     */
+    int mFlags;
+
+    Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
+            @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
+            @NonNull Object lock, @NonNull IBinder activityToken,
+            @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback,
+            int flags, @NonNull ComponentName componentName, @NonNull String packageName) {
+        mService = service;
+        mLock = lock;
+        mUi = ui;
+        mHandlerCaller = handlerCaller;
+        mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
+        mActivityToken = activityToken;
+        mWindowToken = windowToken;
+        mHasCallback = hasCallback;
+        mPackageName = packageName;
+        mFlags = flags;
+
+        mClient = IAutoFillManagerClient.Stub.asInterface(client);
+        try {
+            client.linkToDeath(() -> {
+                if (DEBUG) {
+                    Slog.d(TAG, "app binder died");
+                }
+
+                removeSelf();
+            }, 0);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "linkToDeath() on mClient failed: " + e);
+        }
+
+        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
+    }
+
+    // FillServiceCallbacks
+    @Override
+    public void onFillRequestSuccess(@Nullable FillResponse response,
+            @NonNull String servicePackageName) {
+        if (response == null) {
+            // Nothing to be done, but need to notify client.
+            notifyUnavailableToClient();
+            removeSelf();
+            return;
+        }
+
+        if ((response.getDatasets() == null || response.getDatasets().isEmpty())
+                        && response.getAuthentication() == null) {
+            // Response is "empty" from an UI point of view, need to notify client.
+            notifyUnavailableToClient();
+        }
+        synchronized (mLock) {
+            processResponseLocked(response);
+        }
+
+        LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
+                .setType(MetricsEvent.TYPE_SUCCESS)
+                .setPackageName(mPackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+                        response.getDatasets() == null ? 0 : response.getDatasets().size())
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE,
+                        servicePackageName);
+        mMetricsLogger.write(log);
+    }
+
+    // FillServiceCallbacks
+    @Override
+    public void onFillRequestFailure(@Nullable CharSequence message,
+            @NonNull String servicePackageName) {
+        LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
+                .setType(MetricsEvent.TYPE_FAILURE)
+                .setPackageName(mPackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        mMetricsLogger.write(log);
+
+        getUiForShowing().showError(message);
+        removeSelf();
+    }
+
+    // FillServiceCallbacks
+    @Override
+    public void onSaveRequestSuccess(@NonNull String servicePackageName) {
+        LogMaker log = (new LogMaker(
+                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
+                .setType(MetricsEvent.TYPE_SUCCESS)
+                .setPackageName(mPackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        mMetricsLogger.write(log);
+
+        // Nothing left to do...
+        removeSelf();
+    }
+
+    // FillServiceCallbacks
+    @Override
+    public void onSaveRequestFailure(@Nullable CharSequence message,
+            @NonNull String servicePackageName) {
+        LogMaker log = (new LogMaker(
+                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
+                .setType(MetricsEvent.TYPE_FAILURE)
+                .setPackageName(mPackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        mMetricsLogger.write(log);
+
+        getUiForShowing().showError(message);
+        removeSelf();
+    }
+
+    // FillServiceCallbacks
+    @Override
+    public void authenticate(IntentSender intent) {
+        final Intent fillInIntent;
+        synchronized (mLock) {
+            fillInIntent = createAuthFillInIntent(mStructure);
+        }
+        mHandlerCaller.getHandler().post(() -> startAuthentication(intent, fillInIntent));
+    }
+
+    // FillServiceCallbacks
+    @Override
+    public void onDisableSelf() {
+        mService.disableSelf();
+        synchronized (mLock) {
+            removeSelfLocked();
+        }
+    }
+
+    // FillServiceCallbacks
+    @Override
+    public void onServiceDied(RemoteFillService service) {
+        // TODO(b/33197203): implement
+    }
+
+    // AutoFillUiCallback
+    @Override
+    public void fill(Dataset dataset) {
+        mHandlerCaller.getHandler().post(() -> autoFill(dataset));
+    }
+
+    // AutoFillUiCallback
+    @Override
+    public void save() {
+        mHandlerCaller.getHandler()
+                .obtainMessage(AutofillManagerServiceImpl.MSG_SERVICE_SAVE, mActivityToken)
+                .sendToTarget();
+    }
+
+    // AutoFillUiCallback
+    @Override
+    public void cancelSave() {
+        mHandlerCaller.getHandler().post(() -> removeSelf());
+    }
+
+    // AutoFillUiCallback
+    @Override
+    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) {
+        if (mCurrentResponse == null || data == null) {
+            removeSelf();
+        } else {
+            Parcelable result = data.getParcelable(
+                    AutofillManager.EXTRA_AUTHENTICATION_RESULT);
+            if (result instanceof FillResponse) {
+                mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
+
+                mCurrentResponse = (FillResponse) result;
+                processResponseLocked(mCurrentResponse);
+            } else if (result instanceof Dataset) {
+                Dataset dataset = (Dataset) result;
+                final int index = mCurrentResponse.getDatasets().indexOf(mAutoFilledDataset);
+                if (index >= 0) {
+                    mCurrentResponse.getDatasets().set(index, dataset);
+                    autoFill(dataset);
+                }
+            }
+        }
+    }
+
+    public void setHasCallback(boolean hasIt) {
+        mHasCallback = hasIt;
+    }
+
+    /**
+     * Shows the save UI, when session can be saved.
+     *
+     * @return {@code true} if session is done, or {@code false} if it's pending user action.
+     */
+    public boolean showSaveLocked() {
+        if (mStructure == null) {
+            Slog.wtf(TAG, "showSaveLocked(): no mStructure");
+            return true;
+        }
+        if (mCurrentResponse == null) {
+            // Happens when the activity / session was finished before the service replied, or
+            // when the service cannot autofill it (and returned a null response).
+            if (DEBUG) {
+                Slog.d(TAG, "showSaveLocked(): no mCurrentResponse");
+            }
+            return true;
+        }
+        final SaveInfo saveInfo = mCurrentResponse.getSaveInfo();
+        if (DEBUG) {
+            Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
+        }
+
+        /*
+         * The Save dialog is only shown if all conditions below are met:
+         *
+         * - saveInfo is not null
+         * - autofillValue of all required ids is not null
+         * - autofillValue of at least one id (required or optional) has changed.
+         */
+
+        if (saveInfo == null) {
+            return true;
+        }
+
+        final AutofillId[] requiredIds = saveInfo.getRequiredIds();
+        if (requiredIds == null || requiredIds.length == 0) {
+            Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo");
+            return true;
+        }
+
+        boolean allRequiredAreNotEmpty = true;
+        boolean atLeastOneChanged = false;
+        for (int i = 0; i < requiredIds.length; i++) {
+            final AutofillId id = requiredIds[i];
+            final ViewState state = mViewStates.get(id);
+            if (state == null || state.mAutofillValue == null
+                     || state.mAutofillValue.isEmpty()) {
+                final ViewNode node = findViewNodeByIdLocked(id);
+                if (node == null) {
+                    Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id);
+                    allRequiredAreNotEmpty = false;
+                    break;
+                }
+                final AutofillValue initialValue = node.getAutofillValue();
+                if (initialValue == null || initialValue.isEmpty()) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "finishSessionLocked(): empty initial value for " + id );
+                    }
+                    allRequiredAreNotEmpty = false;
+                    break;
+                }
+            }
+            if (state.mValueUpdated) {
+                final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
+                if (!state.mAutofillValue.equals(filledValue)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
+                                + filledValue + " => " + state.mAutofillValue);
+                    }
+                    atLeastOneChanged = true;
+                }
+            } else {
+                if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": "
+                                + state.mAutofillValue);
+                    }
+                    allRequiredAreNotEmpty = false;
+                    break;
+
+                }
+            }
+        }
+
+        if (allRequiredAreNotEmpty) {
+            if (!atLeastOneChanged && saveInfo.getOptionalIds() != null) {
+                for (int i = 0; i < saveInfo.getOptionalIds().length; i++) {
+                    final AutofillId id = saveInfo.getOptionalIds()[i];
+                    final ViewState state = mViewStates.get(id);
+                    if (state != null && state.mAutofillValue != null && state.mValueUpdated) {
+                        final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
+                        if (!state.mAutofillValue.equals(filledValue)) {
+                            if (DEBUG) {
+                                Slog.d(TAG, "finishSessionLocked(): found a change on optional "
+                                        + id + ": " + filledValue + " => "
+                                        + state.mAutofillValue);
+                            }
+                            atLeastOneChanged = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (atLeastOneChanged) {
+                getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName);
+                return false;
+            }
+        }
+        // Nothing changed...
+        if (DEBUG) {
+            Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
+                    + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
+                    + ", atLeastOneChanged=" + atLeastOneChanged);
+        }
+        return true;
+    }
+
+    /**
+     * Calls service when user requested save.
+     */
+    void callSaveLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
+        }
+
+        final Bundle extras = this.mCurrentResponse.getExtras();
+
+        for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
+            final AutofillValue value = entry.getValue().mAutofillValue;
+            if (value == null) {
+                if (VERBOSE) {
+                    Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey());
+                }
+                continue;
+            }
+            final AutofillId id = entry.getKey();
+            final ViewNode node = findViewNodeByIdLocked(id);
+            if (node == null) {
+                Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
+                continue;
+            }
+            if (VERBOSE) {
+                Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
+            }
+
+            node.updateAutofillValue(value);
+        }
+
+        // Sanitize structure before it's sent to service.
+        mStructure.sanitizeForParceling(false);
+
+        if (VERBOSE) {
+            Slog.v(TAG, "Dumping " + mStructure + " before calling service.save()");
+            mStructure.dump();
+        }
+
+        mRemoteFillService.onSaveRequest(mStructure, extras);
+    }
+
+    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");
+            return;
+        }
+
+        ViewState viewState = mViewStates.get(id);
+        if (viewState == null) {
+            viewState = new ViewState(this, id, this);
+            mViewStates.put(id, viewState);
+        }
+
+        if ((flags & FLAG_START_SESSION) != 0) {
+            // View is triggering autofill.
+            mCurrentViewState = viewState;
+            viewState.update(value, virtualBounds);
+            return;
+        }
+
+        if ((flags & FLAG_VALUE_CHANGED) != 0) {
+            if (value != null && !value.equals(viewState.mAutofillValue)) {
+                viewState.mValueUpdated = true;
+
+                // Must check if this update was caused by autofilling the view, in which
+                // case we just update the value, but not the UI.
+                if (mAutoFilledDataset != null) {
+                    final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
+                    if (value.equals(filledValue)) {
+                        viewState.mAutofillValue = value;
+                        return;
+                    }
+                }
+
+                // Change value
+                viewState.mAutofillValue = value;
+
+                // Update the chooser UI
+                if (value.isText()) {
+                    getUiForShowing().filterFillUi(value.getTextValue().toString());
+                } else {
+                    getUiForShowing().filterFillUi(null);
+                }
+            }
+
+            return;
+        }
+
+        if ((flags & FLAG_VIEW_ENTERED) != 0) {
+            // Remove the UI if the ViewState has changed.
+            if (mCurrentViewState != viewState) {
+                mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null);
+                mCurrentViewState = viewState;
+            }
+
+            // If the ViewState is ready to be displayed, onReady() will be called.
+            viewState.update(value, virtualBounds);
+
+            // TODO(b/33197203): Remove when there is a response per activity.
+            if (mCurrentResponse != null) {
+                viewState.setResponse(mCurrentResponse);
+            }
+
+            return;
+        }
+
+        if ((flags & FLAG_VIEW_EXITED) != 0) {
+            if (mCurrentViewState == viewState) {
+                mUi.hideFillUi(viewState.mId);
+                mCurrentViewState = null;
+            }
+            return;
+        }
+
+        Slog.w(TAG, "updateLocked(): unknown flags " + flags);
+    }
+
+    @Override
+    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, filterText, mPackageName);
+    }
+
+    private void notifyUnavailableToClient() {
+        if (mCurrentViewState == null) {
+            // TODO(b/33197203): temporary sanity check; should never happen
+            Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null");
+            return;
+        }
+        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) {
+        if (DEBUG) {
+            Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication()
+                + "):" + response);
+        }
+
+        if (mCurrentViewState == null) {
+            // TODO(b/33197203): temporary sanity check; should never happen
+            Slog.w(TAG, "processResponseLocked(): mCurrentViewState is null");
+            return;
+        }
+
+        mCurrentResponse = response;
+
+        if (mCurrentResponse.getAuthentication() != null) {
+            // Handle authentication.
+            final Intent fillInIntent = createAuthFillInIntent(mStructure);
+            mCurrentViewState.setResponse(mCurrentResponse, fillInIntent);
+            return;
+        }
+
+        if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null
+                && response.getDatasets().size() == 1) {
+            Slog.d(TAG, "autofilling manual request directly");
+            autoFill(response.getDatasets().get(0));
+            return;
+        }
+
+        mCurrentViewState.setResponse(mCurrentResponse);
+    }
+
+    void autoFill(Dataset dataset) {
+        synchronized (mLock) {
+            mAutoFilledDataset = dataset;
+
+            // Autofill it directly...
+            if (dataset.getAuthentication() == null) {
+                autoFillApp(dataset);
+                return;
+            }
+
+            // ...or handle authentication.
+            Intent fillInIntent = createAuthFillInIntent(mStructure);
+            startAuthentication(dataset.getAuthentication(), fillInIntent);
+        }
+    }
+
+    CharSequence getServiceName() {
+        return mService.getServiceName();
+    }
+
+    private Intent createAuthFillInIntent(AssistStructure structure) {
+        Intent fillInIntent = new Intent();
+        fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure);
+        return fillInIntent;
+    }
+
+    private void startAuthentication(IntentSender intent, Intent fillInIntent) {
+        try {
+            mClient.authenticate(intent, fillInIntent);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error launching auth intent", e);
+        }
+    }
+
+    void dumpLocked(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
+        pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags);
+        pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
+        pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
+        pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState);
+        pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size());
+        final String prefix2 = prefix + "  ";
+        for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
+            pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
+            entry.getValue().dump(prefix2, pw);
+        }
+        if (VERBOSE) {
+            pw.print(prefix); pw.print("mStructure: " );
+            // TODO(b/33197203): add method do dump AssistStructure on pw
+            if (mStructure != null) {
+                pw.println("look at logcat" );
+                mStructure.dump(); // dumps to logcat
+            } else {
+                pw.println("null");
+            }
+        }
+        pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
+        mRemoteFillService.dump(prefix, pw);
+    }
+
+    void autoFillApp(Dataset dataset) {
+        synchronized (mLock) {
+            try {
+                if (DEBUG) {
+                    Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
+                }
+                mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues());
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Error autofilling activity: " + e);
+            }
+        }
+    }
+
+    private AutoFillUI getUiForShowing() {
+        synchronized (mLock) {
+            mUi.setCallback(this);
+            return mUi;
+        }
+    }
+
+    private ViewNode findViewNodeByIdLocked(AutofillId id) {
+        final int size = mStructure.getWindowNodeCount();
+        for (int i = 0; i < size; i++) {
+            final WindowNode window = mStructure.getWindowNodeAt(i);
+            final ViewNode root = window.getRootViewNode();
+            if (id.equals(root.getAutofillId())) {
+                return root;
+            }
+            final ViewNode child = findViewNodeByIdLocked(root, id);
+            if (child != null) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    private ViewNode findViewNodeByIdLocked(ViewNode parent, AutofillId id) {
+        final int childrenSize = parent.getChildCount();
+        if (childrenSize > 0) {
+            for (int i = 0; i < childrenSize; i++) {
+                final ViewNode child = parent.getChildAt(i);
+                if (id.equals(child.getAutofillId())) {
+                    return child;
+                }
+                final ViewNode grandChild = findViewNodeByIdLocked(child, id);
+                if (grandChild != null && id.equals(grandChild.getAutofillId())) {
+                    return grandChild;
+                }
+            }
+        }
+        return null;
+    }
+
+    void destroyLocked() {
+        mRemoteFillService.destroy();
+        mUi.setCallback(null);
+        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
+    }
+
+    void removeSelf() {
+        synchronized (mLock) {
+            removeSelfLocked();
+        }
+    }
+
+    void removeSelfLocked() {
+        if (VERBOSE) {
+            Slog.v(TAG, "removeSelfLocked()");
+        }
+        destroyLocked();
+        mService.removeSessionLocked(mActivityToken);
+    }
+}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
new file mode 100644
index 0000000..d31dcfd
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -0,0 +1,125 @@
+/*
+ * 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.autofill;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.service.autofill.FillResponse;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import java.io.PrintWriter;
+
+/**
+ * State for a given view with a AutofillId.
+ *
+ * <p>This class holds state about a view and calls its listener when the fill UI is ready to
+ * be displayed for the view.
+ */
+final class ViewState {
+    interface Listener {
+        /**
+         * Called when the fill UI is ready to be shown for this view.
+         */
+        void onFillReady(FillResponse fillResponse, AutofillId focusedId,
+                @Nullable AutofillValue value);
+    }
+
+    final AutofillId mId;
+    private final Listener mListener;
+    // TODO(b/33197203): would not need a reference to response and session if it was an inner
+    // class of Session...
+    private final Session mSession;
+    private FillResponse mResponse;
+    private Intent mAuthIntent;
+
+    // TODO(b/33197203): encapsulate access so it's not called by UI
+    AutofillValue mAutofillValue;
+
+    // TODO(b/33197203): encapsulate access so it's not called by UI
+    // Bounds if a virtual view, null otherwise
+    Rect mVirtualBounds;
+
+    boolean mValueUpdated;
+
+    ViewState(Session session, AutofillId id, Listener listener) {
+        mSession = session;
+        mId = id;
+        mListener = listener;
+    }
+
+    /**
+     * Response should only be set once.
+     */
+    void setResponse(FillResponse response) {
+        mResponse = response;
+        maybeCallOnFillReady();
+    }
+
+    /**
+     * Used when a {@link FillResponse} requires authentication to be unlocked.
+     */
+    void setResponse(FillResponse response, Intent authIntent) {
+        mAuthIntent = authIntent;
+        setResponse(response);
+    }
+
+    CharSequence getServiceName() {
+        return mSession.getServiceName();
+    }
+
+    // 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 virtualBounds) {
+        if (autofillValue != null) {
+            mAutofillValue = autofillValue;
+        }
+        if (virtualBounds != null) {
+            mVirtualBounds = virtualBounds;
+        }
+
+        maybeCallOnFillReady();
+    }
+
+    /**
+     * Calls {@link
+     * 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)) {
+            mListener.onFillReady(mResponse, mId, mAutofillValue);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds
+                + ", updated = " + mValueUpdated + "]";
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        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("virtualBounds:" ); pw.println(mVirtualBounds);
+        pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java
index 5b627d9..1a3bb35 100644
--- a/services/core/java/com/android/server/NetworkScorerAppManager.java
+++ b/services/core/java/com/android/server/NetworkScorerAppManager.java
@@ -88,9 +88,12 @@
                 final String serviceLabel = getRecommendationServiceLabel(serviceInfo, pm);
                 final ComponentName useOpenWifiNetworksActivity =
                         findUseOpenWifiNetworksActivity(serviceInfo);
+                final String networkAvailableNotificationChannelId =
+                        getNetworkAvailableNotificationChannelId(serviceInfo);
                 appDataList.add(
                         new NetworkScorerAppData(serviceInfo.applicationInfo.uid,
-                                serviceComponentName, serviceLabel, useOpenWifiNetworksActivity));
+                                serviceComponentName, serviceLabel, useOpenWifiNetworksActivity,
+                                networkAvailableNotificationChannelId));
             } else {
                 if (VERBOSE) Log.v(TAG, serviceInfo.packageName
                         + " is NOT a valid scorer/recommender.");
@@ -145,6 +148,20 @@
         return null;
     }
 
+    @Nullable
+    private static String getNetworkAvailableNotificationChannelId(ServiceInfo serviceInfo) {
+        if (serviceInfo.metaData == null) {
+            if (DEBUG) {
+                Log.d(TAG, "No metadata found on " + serviceInfo.getComponentName());
+            }
+            return null;
+        }
+
+        return serviceInfo.metaData.getString(
+                NetworkScoreManager.NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID_META_DATA);
+    }
+
+
     /**
      * Get the application to use for scoring networks.
      *
diff --git a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
index c3b7e15..6380da5 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
@@ -100,18 +100,20 @@
             Account account = null;
             AccountManagerService.UserAccounts accounts = mAccountManagerService
                     .getUserAccounts(userId);
-            synchronized (accounts.cacheLock) {
-                for (Account[] accountsPerType : accounts.accountCache.values()) {
-                    for (Account accountPerType : accountsPerType) {
-                        if (accountDigest.equals(PackageUtils.computeSha256Digest(
-                                accountPerType.name.getBytes()))) {
-                            account = accountPerType;
+            synchronized (accounts.dbLock) {
+                synchronized (accounts.cacheLock) {
+                    for (Account[] accountsPerType : accounts.accountCache.values()) {
+                        for (Account accountPerType : accountsPerType) {
+                            if (accountDigest.equals(PackageUtils.computeSha256Digest(
+                                    accountPerType.name.getBytes()))) {
+                                account = accountPerType;
+                                break;
+                            }
+                        }
+                        if (account != null) {
                             break;
                         }
                     }
-                    if (account != null) {
-                        break;
-                    }
                 }
             }
             if (account == null) {
@@ -141,49 +143,52 @@
     public byte[] backupAccountAccessPermissions(int userId) {
         final AccountManagerService.UserAccounts accounts = mAccountManagerService
                 .getUserAccounts(userId);
-        synchronized (accounts.cacheLock) {
-            List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
-                    .findAllAccountGrants();
-            if (allAccountGrants.isEmpty()) {
-                return null;
-            }
-            try {
-                ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
-                final XmlSerializer serializer = new FastXmlSerializer();
-                serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
-                serializer.startDocument(null, true);
-                serializer.startTag(null, TAG_PERMISSIONS);
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
+                        .findAllAccountGrants();
+                if (allAccountGrants.isEmpty()) {
+                    return null;
+                }
+                try {
+                    ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+                    final XmlSerializer serializer = new FastXmlSerializer();
+                    serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
+                    serializer.startDocument(null, true);
+                    serializer.startTag(null, TAG_PERMISSIONS);
 
-                PackageManager packageManager = mAccountManagerService.mContext.getPackageManager();
-                for (Pair<String, Integer> grant : allAccountGrants) {
-                    final String accountName = grant.first;
-                    final int uid = grant.second;
+                    PackageManager packageManager = mAccountManagerService.mContext
+                            .getPackageManager();
+                    for (Pair<String, Integer> grant : allAccountGrants) {
+                        final String accountName = grant.first;
+                        final int uid = grant.second;
 
-                    final String[] packageNames = packageManager.getPackagesForUid(uid);
-                    if (packageNames == null) {
-                        continue;
-                    }
+                        final String[] packageNames = packageManager.getPackagesForUid(uid);
+                        if (packageNames == null) {
+                            continue;
+                        }
 
-                    for (String packageName : packageNames) {
-                        String digest = PackageUtils.computePackageCertSha256Digest(
-                                packageManager, packageName, userId);
-                        if (digest != null) {
-                            serializer.startTag(null, TAG_PERMISSION);
-                            serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
-                                    PackageUtils.computeSha256Digest(accountName.getBytes()));
-                            serializer.attribute(null, ATTR_PACKAGE, packageName);
-                            serializer.attribute(null, ATTR_DIGEST, digest);
-                            serializer.endTag(null, TAG_PERMISSION);
+                        for (String packageName : packageNames) {
+                            String digest = PackageUtils.computePackageCertSha256Digest(
+                                    packageManager, packageName, userId);
+                            if (digest != null) {
+                                serializer.startTag(null, TAG_PERMISSION);
+                                serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
+                                        PackageUtils.computeSha256Digest(accountName.getBytes()));
+                                serializer.attribute(null, ATTR_PACKAGE, packageName);
+                                serializer.attribute(null, ATTR_DIGEST, digest);
+                                serializer.endTag(null, TAG_PERMISSION);
+                            }
                         }
                     }
+                    serializer.endTag(null, TAG_PERMISSIONS);
+                    serializer.endDocument();
+                    serializer.flush();
+                    return dataStream.toByteArray();
+                } catch (IOException e) {
+                    Log.e(TAG, "Error backing up account access grants", e);
+                    return null;
                 }
-                serializer.endTag(null, TAG_PERMISSIONS);
-                serializer.endDocument();
-                serializer.flush();
-                return dataStream.toByteArray();
-            } catch (IOException e) {
-                Log.e(TAG, "Error backing up account access grants", e);
-                return null;
             }
         }
     }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 8e3e3ea..79be3e6 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -201,17 +201,19 @@
         private final HashMap<Account, Integer> signinRequiredNotificationIds =
                 new HashMap<Account, Integer>();
         final Object cacheLock = new Object();
+        final Object dbLock = 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
          */
@@ -237,8 +239,10 @@
 
         UserAccounts(Context context, int userId, File preNDbFile, File deDbFile) {
             this.userId = userId;
-            synchronized (cacheLock) {
-                accountsDb = AccountsDb.create(context, userId, preNDbFile, deDbFile);
+            synchronized (dbLock) {
+                synchronized (cacheLock) {
+                    accountsDb = AccountsDb.create(context, userId, preNDbFile, deDbFile);
+                }
             }
         }
     }
@@ -499,12 +503,14 @@
 
         Map<Account, Integer> result = new LinkedHashMap<>();
         for (String accountType : accountTypes) {
-            synchronized (accounts.cacheLock) {
-                final Account[] accountsOfType = accounts.accountCache.get(accountType);
-                if (accountsOfType != null) {
-                    for (Account account : accountsOfType) {
-                        result.put(account,
-                                resolveAccountVisibility(account, packageName, accounts));
+            synchronized (accounts.dbLock) {
+                synchronized (accounts.cacheLock) {
+                    final Account[] accountsOfType = accounts.accountCache.get(accountType);
+                    if (accountsOfType != null) {
+                        for (Account account : accountsOfType) {
+                            result.put(account,
+                                    resolveAccountVisibility(account, packageName, accounts));
+                        }
                     }
                 }
             }
@@ -524,25 +530,31 @@
                     String.format("uid %s cannot get secrets for account %s", callingUid, account);
             throw new SecurityException(msg);
         }
-        return getPackagesAndVisibilityForAccount(account, accounts);
+        synchronized (accounts.dbLock) {
+            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 +584,15 @@
      * @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);
-            return visibility != null ? visibility : AccountManager.VISIBILITY_UNDEFINED;
-        } finally {
-            StrictMode.setThreadPolicy(oldPolicy);
+    private int getAccountVisibilityFromCache(Account account, String packageName,
+            UserAccounts accounts) {
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                Map<String, Integer> accountVisibility =
+                        getPackagesAndVisibilityForAccountLocked(account, accounts);
+                Integer visibility = accountVisibility.get(packageName);
+                return visibility != null ? visibility : AccountManager.VISIBILITY_UNDEFINED;
+            }
         }
     }
 
@@ -595,9 +608,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 +641,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 +663,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;
@@ -727,55 +738,71 @@
      */
     private boolean setAccountVisibility(Account account, String packageName, int newVisibility,
             boolean notify, UserAccounts accounts) {
-        synchronized (accounts.cacheLock) {
-            Map<String, Integer> packagesToVisibility;
-            if (notify) {
-                if (isSpecialPackageKey(packageName)) {
-                    packagesToVisibility =
-                        getRequestingPackages(account, accounts);
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                Map<String, Integer> packagesToVisibility;
+                if (notify) {
+                    if (isSpecialPackageKey(packageName)) {
+                        packagesToVisibility =
+                                getRequestingPackages(account, accounts);
+                    } else {
+                        if (!packageExistsForUser(packageName, accounts.userId)) {
+                            return false; // package is not installed.
+                        }
+                        packagesToVisibility = new HashMap<>();
+                        packagesToVisibility.put(packageName,
+                                resolveAccountVisibility(account, packageName, accounts));
+                    }
                 } else {
-                    if (!packageExistsForUser(packageName, accounts.userId)) {
-                        return false; // package is not installed.
+                    // Notifications will not be send.
+                    if (!isSpecialPackageKey(packageName) &&
+                            !packageExistsForUser(packageName, accounts.userId)) {
+                        // package is not installed and not meta value.
+                        return false;
                     }
                     packagesToVisibility = new HashMap<>();
-                    packagesToVisibility.put(packageName,
-                        resolveAccountVisibility(account, packageName, accounts));
                 }
-            } else {
-                // Notifications will not be send.
-                if (!isSpecialPackageKey(packageName) &&
-                        !packageExistsForUser(packageName, accounts.userId)) {
-                    // package is not installed and not meta value.
+
+                if (!updateAccountVisibilityLocked(account, packageName, newVisibility, accounts)) {
                     return false;
                 }
-                packagesToVisibility = new HashMap<>();
-            }
 
-            final long accountId = accounts.accountsDb.findDeAccountId(account);
-            if (accountId < 0) {
+                if (notify) {
+                    for (Entry<String, Integer> packageToVisibility : packagesToVisibility
+                            .entrySet()) {
+                        if (packageToVisibility.getValue()
+                                != AccountManager.VISIBILITY_NOT_VISIBLE) {
+                            notifyPackage(packageToVisibility.getKey(), accounts);
+                        }
+                    }
+                    sendAccountsChangedBroadcast(accounts.userId);
+                }
+                return true;
+            }
+        }
+    }
+
+    // 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;
             }
-
-            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) {
-                        notifyPackage(packageToVisibility.getKey(), accounts);
-                    }
-                }
-                sendAccountsChangedBroadcast(accounts.userId);
-            }
-            return true;
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
         }
+        Map<String, Integer> accountVisibility =
+            getPackagesAndVisibilityForAccountLocked(account, accounts);
+        accountVisibility.put(packageName, newVisibility);
+        return true;
     }
 
     @Override
@@ -956,23 +983,24 @@
                 mAuthenticatorCache, accounts.userId);
         boolean userUnlocked = isLocalUnlockedUser(accounts.userId);
 
-        synchronized (accounts.cacheLock) {
-            boolean accountDeleted = false;
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                boolean accountDeleted = false;
 
-            // Get a map of stored authenticator types to UID
-            final AccountsDb accountsDb = accounts.accountsDb;
-            Map<String, Integer> metaAuthUid = accountsDb.findMetaAuthUid();
-            // Create a list of authenticator type whose previous uid no longer exists
-            HashSet<String> obsoleteAuthType = Sets.newHashSet();
-            SparseBooleanArray knownUids = null;
-            for (Entry<String, Integer> authToUidEntry : metaAuthUid.entrySet()) {
-                String type = authToUidEntry.getKey();
-                int uid = authToUidEntry.getValue();
-                Integer knownUid = knownAuth.get(type);
-                if (knownUid != null && uid == knownUid) {
-                    // Remove it from the knownAuth list if it's unchanged.
-                    knownAuth.remove(type);
-                } else {
+                // Get a map of stored authenticator types to UID
+                final AccountsDb accountsDb = accounts.accountsDb;
+                Map<String, Integer> metaAuthUid = accountsDb.findMetaAuthUid();
+                // Create a list of authenticator type whose previous uid no longer exists
+                HashSet<String> obsoleteAuthType = Sets.newHashSet();
+                SparseBooleanArray knownUids = null;
+                for (Entry<String, Integer> authToUidEntry : metaAuthUid.entrySet()) {
+                    String type = authToUidEntry.getKey();
+                    int uid = authToUidEntry.getValue();
+                    Integer knownUid = knownAuth.get(type);
+                    if (knownUid != null && uid == knownUid) {
+                        // Remove it from the knownAuth list if it's unchanged.
+                        knownAuth.remove(type);
+                    } else {
                     /*
                      * The authenticator is presently not cached and should only be triggered
                      * when we think an authenticator has been removed (or is being updated).
@@ -989,87 +1017,95 @@
                      * uninstalled while the authenticator's package is being updated.
                      *
                      */
-                    if (knownUids == null) {
-                        knownUids = getUidsOfInstalledOrUpdatedPackagesAsUser(accounts.userId);
-                    }
-                    if (!knownUids.get(uid)) {
-                        // The authenticator is not presently available to the cache. And the
-                        // package no longer has a data directory (so we surmise it isn't updating).
-                        // So purge its data from the account databases.
-                        obsoleteAuthType.add(type);
-                        // And delete it from the TABLE_META
-                        accountsDb.deleteMetaByAuthTypeAndUid(type, uid);
+                        if (knownUids == null) {
+                            knownUids = getUidsOfInstalledOrUpdatedPackagesAsUser(accounts.userId);
+                        }
+                        if (!knownUids.get(uid)) {
+                            // The authenticator is not presently available to the cache. And the
+                            // package no longer has a data directory (so we surmise it isn't
+                            // updating). So purge its data from the account databases.
+                            obsoleteAuthType.add(type);
+                            // And delete it from the TABLE_META
+                            accountsDb.deleteMetaByAuthTypeAndUid(type, uid);
+                        }
                     }
                 }
-            }
 
-            // Add the newly registered authenticator to TABLE_META. If old authenticators have
-            // been re-enabled (after being updated for example), then we just overwrite the old
-            // values.
-            for (Entry<String, Integer> entry : knownAuth.entrySet()) {
-                accountsDb.insertOrReplaceMetaAuthTypeAndUid(entry.getKey(), entry.getValue());
-            }
+                // Add the newly registered authenticator to TABLE_META. If old authenticators have
+                // been re-enabled (after being updated for example), then we just overwrite the old
+                // values.
+                for (Entry<String, Integer> entry : knownAuth.entrySet()) {
+                    accountsDb.insertOrReplaceMetaAuthTypeAndUid(entry.getKey(), entry.getValue());
+                }
 
-            final Map<Long, Account> accountsMap = accountsDb.findAllDeAccounts();
-            try {
-                accounts.accountCache.clear();
-                final HashMap<String, ArrayList<String>> accountNamesByType = new LinkedHashMap<>();
-                for (Entry<Long, Account> accountEntry : accountsMap.entrySet()) {
-                    final long accountId = accountEntry.getKey();
-                    final Account account = accountEntry.getValue();
-                    if (obsoleteAuthType.contains(account.type)) {
-                        Slog.w(TAG, "deleting account " + account.name + " because type "
-                                + account.type + "'s registered authenticator no longer exist.");
-                        Map<String, Integer> packagesToVisibility =
-                            getRequestingPackages(account, accounts);
-                        accountsDb.beginTransaction();
-                        try {
-                            accountsDb.deleteDeAccount(accountId);
-                            // Also delete from CE table if user is unlocked; if user is currently
-                            // locked the account will be removed later by syncDeCeAccountsLocked
-                            if (userUnlocked) {
-                                accountsDb.deleteCeAccount(accountId);
+                final Map<Long, Account> accountsMap = accountsDb.findAllDeAccounts();
+                try {
+                    accounts.accountCache.clear();
+                    final HashMap<String, ArrayList<String>> accountNamesByType
+                            = new LinkedHashMap<>();
+                    for (Entry<Long, Account> accountEntry : accountsMap.entrySet()) {
+                        final long accountId = accountEntry.getKey();
+                        final Account account = accountEntry.getValue();
+                        if (obsoleteAuthType.contains(account.type)) {
+                            Slog.w(TAG, "deleting account " + account.name + " because type "
+                                    + account.type
+                                    + "'s registered authenticator no longer exist.");
+                            Map<String, Integer> packagesToVisibility =
+                                    getRequestingPackages(account, accounts);
+                            accountsDb.beginTransaction();
+                            try {
+                                accountsDb.deleteDeAccount(accountId);
+                                // Also delete from CE table if user is unlocked; if user is
+                                // currently locked the account will be removed later by
+                                // syncDeCeAccountsLocked
+                                if (userUnlocked) {
+                                    accountsDb.deleteCeAccount(accountId);
+                                }
+                                accountsDb.setTransactionSuccessful();
+                            } finally {
+                                accountsDb.endTransaction();
                             }
-                            accountsDb.setTransactionSuccessful();
-                        } finally {
-                            accountsDb.endTransaction();
-                        }
-                        accountDeleted = true;
+                            accountDeleted = true;
 
-                        logRecord(AccountsDb.DEBUG_ACTION_AUTHENTICATOR_REMOVE,
-                                AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
+                            logRecord(AccountsDb.DEBUG_ACTION_AUTHENTICATOR_REMOVE,
+                                    AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
 
-                        accounts.userDataCache.remove(account);
-                        accounts.authTokenCache.remove(account);
-                        accounts.accountTokenCaches.remove(account);
+                            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) {
-                                notifyPackage(packageToVisibility.getKey(), accounts);
+                            for (Entry<String, Integer> packageToVisibility :
+                                    packagesToVisibility.entrySet()) {
+                                if (packageToVisibility.getValue()
+                                        != AccountManager.VISIBILITY_NOT_VISIBLE) {
+                                    notifyPackage(packageToVisibility.getKey(), accounts);
+                                }
                             }
+                        } else {
+                            ArrayList<String> accountNames = accountNamesByType.get(account.type);
+                            if (accountNames == null) {
+                                accountNames = new ArrayList<>();
+                                accountNamesByType.put(account.type, accountNames);
+                            }
+                            accountNames.add(account.name);
                         }
-                    } else {
-                        ArrayList<String> accountNames = accountNamesByType.get(account.type);
-                        if (accountNames == null) {
-                            accountNames = new ArrayList<>();
-                            accountNamesByType.put(account.type, accountNames);
+                    }
+                    for (Map.Entry<String, ArrayList<String>> cur : accountNamesByType.entrySet()) {
+                        final String accountType = cur.getKey();
+                        final ArrayList<String> accountNames = cur.getValue();
+                        final Account[] accountsForType = new Account[accountNames.size()];
+                        for (int i = 0; i < accountsForType.length; i++) {
+                            accountsForType[i] = new Account(accountNames.get(i), accountType,
+                                    UUID.randomUUID().toString());
                         }
-                        accountNames.add(account.name);
+                        accounts.accountCache.put(accountType, accountsForType);
                     }
-                }
-                for (Map.Entry<String, ArrayList<String>> cur : accountNamesByType.entrySet()) {
-                    final String accountType = cur.getKey();
-                    final ArrayList<String> accountNames = cur.getValue();
-                    final Account[] accountsForType = new Account[accountNames.size()];
-                    for (int i = 0; i < accountsForType.length; i++) {
-                        accountsForType[i] = new Account(accountNames.get(i), accountType,
-                                UUID.randomUUID().toString());
+                    accounts.visibilityCache.putAll(accountsDb.findAllVisibilityValues());
+                } finally {
+                    if (accountDeleted) {
+                        sendAccountsChangedBroadcast(accounts.userId);
                     }
-                    accounts.accountCache.put(accountType, accountsForType);
-                }
-            } finally {
-                if (accountDeleted) {
-                    sendAccountsChangedBroadcast(accounts.userId);
                 }
             }
         }
@@ -1129,9 +1165,11 @@
             // open CE database if necessary
             if (!accounts.accountsDb.isCeDatabaseAttached() && mLocalUnlockedUsers.get(userId)) {
                 Log.i(TAG, "User " + userId + " is unlocked - opening CE database");
-                synchronized (accounts.cacheLock) {
-                    File ceDatabaseFile = new File(mInjector.getCeDatabaseName(userId));
-                    accounts.accountsDb.attachCeDatabase(ceDatabaseFile);
+                synchronized (accounts.dbLock) {
+                    synchronized (accounts.cacheLock) {
+                        File ceDatabaseFile = new File(mInjector.getCeDatabaseName(userId));
+                        accounts.accountsDb.attachCeDatabase(ceDatabaseFile);
+                    }
                 }
                 syncDeCeAccountsLocked(accounts);
             }
@@ -1166,34 +1204,50 @@
     }
 
     private void purgeOldGrants(UserAccounts accounts) {
-        synchronized (accounts.cacheLock) {
-            List<Integer> uids = accounts.accountsDb.findAllUidGrants();
-            for (int uid : uids) {
-                final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
-                if (packageExists) {
-                    continue;
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                List<Integer> uids = accounts.accountsDb.findAllUidGrants();
+                for (int uid : uids) {
+                    final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
+                    if (packageExists) {
+                        continue;
+                    }
+                    Log.d(TAG, "deleting grants for UID " + uid
+                            + " because its package is no longer installed");
+                    accounts.accountsDb.deleteGrantsByUid(uid);
                 }
-                Log.d(TAG, "deleting grants for UID " + uid
-                        + " because its package is no longer installed");
-                accounts.accountsDb.deleteGrantsByUid(uid);
             }
         }
     }
 
     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.dbLock) {
+                        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;
@@ -1203,8 +1257,10 @@
             mLocalUnlockedUsers.delete(userId);
         }
         if (accounts != null) {
-            synchronized (accounts.cacheLock) {
-                accounts.accountsDb.close();
+            synchronized (accounts.dbLock) {
+                synchronized (accounts.cacheLock) {
+                    accounts.accountsDb.close();
+                }
             }
         }
     }
@@ -1284,8 +1340,11 @@
             return null;
         }
 
-        synchronized (accounts.cacheLock) {
-            return accounts.accountsDb.findAccountPasswordByNameAndType(account.name, account.type);
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                return accounts.accountsDb
+                        .findAccountPasswordByNameAndType(account.name, account.type);
+            }
         }
     }
 
@@ -1311,15 +1370,17 @@
         if  (account == null) {
             return null;
         }
-        synchronized (accounts.cacheLock) {
-            AtomicReference<String> previousNameRef = accounts.previousNameCache.get(account);
-            if (previousNameRef == null) {
-                String previousName = accounts.accountsDb.findDeAccountPreviousName(account);
-                previousNameRef = new AtomicReference<>(previousName);
-                accounts.previousNameCache.put(account, previousNameRef);
-                return previousName;
-            } else {
-                return previousNameRef.get();
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                AtomicReference<String> previousNameRef = accounts.previousNameCache.get(account);
+                if (previousNameRef == null) {
+                    String previousName = accounts.accountsDb.findDeAccountPreviousName(account);
+                    previousNameRef = new AtomicReference<>(previousName);
+                    accounts.previousNameCache.put(account, previousNameRef);
+                    return previousName;
+                } else {
+                    return previousNameRef.get();
+                }
             }
         }
     }
@@ -1349,11 +1410,13 @@
         long identityToken = clearCallingIdentity();
         try {
             UserAccounts accounts = getUserAccounts(userId);
-            synchronized (accounts.cacheLock) {
-                if (!accountExistsCacheLocked(accounts, account)) {
-                    return null;
+            synchronized (accounts.dbLock) {
+                synchronized (accounts.cacheLock) {
+                    if (!accountExistsCacheLocked(accounts, account)) {
+                        return null;
+                    }
+                    return readUserDataInternalLocked(accounts, account, key);
                 }
-                return readUserDataInternalLocked(accounts, account, key);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -1512,8 +1575,10 @@
 
     private boolean updateLastAuthenticatedTime(Account account) {
         final UserAccounts accounts = getUserAccountsForCaller();
-        synchronized (accounts.cacheLock) {
-            return accounts.accountsDb.updateAccountLastAuthenticatedTime(account);
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                return accounts.accountsDb.updateAccountLastAuthenticatedTime(account);
+            }
         }
     }
 
@@ -1536,13 +1601,15 @@
                 public void run() throws RemoteException {
                     // Confirm that the owner's account still exists before this step.
                     UserAccounts owner = getUserAccounts(parentUserId);
-                    synchronized (owner.cacheLock) {
-                        for (Account acc : getAccounts(parentUserId,
-                                mContext.getOpPackageName())) {
-                            if (acc.equals(account)) {
-                                mAuthenticator.addAccountFromCredentials(
-                                        this, account, accountCredentials);
-                                break;
+                    synchronized (owner.dbLock) {
+                        synchronized (owner.cacheLock) {
+                            for (Account acc : getAccounts(parentUserId,
+                                    mContext.getOpPackageName())) {
+                                if (acc.equals(account)) {
+                                    mAuthenticator.addAccountFromCredentials(
+                                            this, account, accountCredentials);
+                                    break;
+                                }
                             }
                         }
                     }
@@ -1583,51 +1650,55 @@
                     + " is locked. callingUid=" + callingUid);
             return false;
         }
-        synchronized (accounts.cacheLock) {
-            accounts.accountsDb.beginTransaction();
-            try {
-                if (accounts.accountsDb.findCeAccountId(account) >= 0) {
-                    Log.w(TAG, "insertAccountIntoDatabase: " + account
-                            + ", skipping since the account already exists");
-                    return false;
-                }
-                long accountId = accounts.accountsDb.insertCeAccount(account, password);
-                if (accountId < 0) {
-                    Log.w(TAG, "insertAccountIntoDatabase: " + account
-                            + ", skipping the DB insert failed");
-                    return false;
-                }
-                // Insert into DE table
-                if (accounts.accountsDb.insertDeAccount(account, accountId) < 0) {
-                    Log.w(TAG, "insertAccountIntoDatabase: " + account
-                            + ", skipping the DB insert failed");
-                    return false;
-                }
-                if (extras != null) {
-                    for (String key : extras.keySet()) {
-                        final String value = extras.getString(key);
-                        if (accounts.accountsDb.insertExtra(accountId, key, value) < 0) {
-                            Log.w(TAG, "insertAccountIntoDatabase: " + account
-                                    + ", skipping since insertExtra failed for key " + key);
-                            return false;
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                accounts.accountsDb.beginTransaction();
+                try {
+                    if (accounts.accountsDb.findCeAccountId(account) >= 0) {
+                        Log.w(TAG, "insertAccountIntoDatabase: " + account
+                                + ", skipping since the account already exists");
+                        return false;
+                    }
+                    long accountId = accounts.accountsDb.insertCeAccount(account, password);
+                    if (accountId < 0) {
+                        Log.w(TAG, "insertAccountIntoDatabase: " + account
+                                + ", skipping the DB insert failed");
+                        return false;
+                    }
+                    // Insert into DE table
+                    if (accounts.accountsDb.insertDeAccount(account, accountId) < 0) {
+                        Log.w(TAG, "insertAccountIntoDatabase: " + account
+                                + ", skipping the DB insert failed");
+                        return false;
+                    }
+                    if (extras != null) {
+                        for (String key : extras.keySet()) {
+                            final String value = extras.getString(key);
+                            if (accounts.accountsDb.insertExtra(accountId, key, value) < 0) {
+                                Log.w(TAG, "insertAccountIntoDatabase: " + account
+                                        + ", skipping since insertExtra failed for key " + key);
+                                return false;
+                            }
                         }
                     }
-                }
 
-                if (packageToVisibility != null) {
-                    for (Entry<String, Integer> entry : packageToVisibility.entrySet()) {
-                        setAccountVisibility(account, entry.getKey() /* package */,
-                                entry.getValue() /* visibility */, false /* notify */, accounts);
+                    if (packageToVisibility != null) {
+                        for (Entry<String, Integer> entry : packageToVisibility.entrySet()) {
+                            setAccountVisibility(account, entry.getKey() /* package */,
+                                    entry.getValue() /* visibility */, false /* notify */,
+                                    accounts);
+                        }
                     }
+                    accounts.accountsDb.setTransactionSuccessful();
+
+                    logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS,
+                            accountId,
+                            accounts, callingUid);
+
+                    insertAccountIntoCacheLocked(accounts, account);
+                } finally {
+                    accounts.accountsDb.endTransaction();
                 }
-                accounts.accountsDb.setTransactionSuccessful();
-
-                logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS, accountId,
-                        accounts, callingUid);
-
-                insertAccountIntoCacheLocked(accounts, account);
-            } finally {
-                accounts.accountsDb.endTransaction();
             }
         }
         if (getUserManager().getUserInfo(accounts.userId).canHaveProfile()) {
@@ -1812,72 +1883,76 @@
                 }
             }
         }
-        synchronized (accounts.cacheLock) {
-            accounts.accountsDb.beginTransaction();
-            Account renamedAccount = new Account(newName, accountToRename.type);
-            if ((accounts.accountsDb.findCeAccountId(renamedAccount) >= 0)) {
-                Log.e(TAG, "renameAccount failed - account with new name already exists");
-                return null;
-            }
-            try {
-                final long accountId = accounts.accountsDb.findDeAccountId(accountToRename);
-                if (accountId >= 0) {
-                    accounts.accountsDb.renameCeAccount(accountId, newName);
-                    if (accounts.accountsDb.renameDeAccount(
-                            accountId, newName, accountToRename.name)) {
-                        accounts.accountsDb.setTransactionSuccessful();
-                    } else {
-                        Log.e(TAG, "renameAccount failed");
-                        return null;
-                    }
-                } else {
-                    Log.e(TAG, "renameAccount failed - old account does not exist");
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                accounts.accountsDb.beginTransaction();
+                Account renamedAccount = new Account(newName, accountToRename.type);
+                if ((accounts.accountsDb.findCeAccountId(renamedAccount) >= 0)) {
+                    Log.e(TAG, "renameAccount failed - account with new name already exists");
                     return null;
                 }
-            } finally {
-                accounts.accountsDb.endTransaction();
-            }
+                try {
+                    final long accountId = accounts.accountsDb.findDeAccountId(accountToRename);
+                    if (accountId >= 0) {
+                        accounts.accountsDb.renameCeAccount(accountId, newName);
+                        if (accounts.accountsDb.renameDeAccount(
+                                accountId, newName, accountToRename.name)) {
+                            accounts.accountsDb.setTransactionSuccessful();
+                        } else {
+                            Log.e(TAG, "renameAccount failed");
+                            return null;
+                        }
+                    } else {
+                        Log.e(TAG, "renameAccount failed - old account does not exist");
+                        return null;
+                    }
+                } finally {
+                    accounts.accountsDb.endTransaction();
+                }
             /*
              * Database transaction was successful. Clean up cached
              * data associated with the account in the user profile.
              */
-            renamedAccount = insertAccountIntoCacheLocked(accounts, renamedAccount);
+                renamedAccount = insertAccountIntoCacheLocked(accounts, renamedAccount);
             /*
              * Extract the data and token caches before removing the
              * old account to preserve the user data associated with
              * the account.
              */
-            Map<String, String> tmpData = accounts.userDataCache.get(accountToRename);
-            Map<String, String> tmpTokens = accounts.authTokenCache.get(accountToRename);
-            removeAccountFromCacheLocked(accounts, accountToRename);
+                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
              * account.
              */
-            accounts.userDataCache.put(renamedAccount, tmpData);
-            accounts.authTokenCache.put(renamedAccount, tmpTokens);
-            accounts.previousNameCache.put(
-                    renamedAccount,
-                    new AtomicReference<>(accountToRename.name));
-            resultAccount = renamedAccount;
+                accounts.userDataCache.put(renamedAccount, tmpData);
+                accounts.authTokenCache.put(renamedAccount, tmpTokens);
+                accounts.visibilityCache.put(renamedAccount, tmpVisibility);
+                accounts.previousNameCache.put(
+                        renamedAccount,
+                        new AtomicReference<>(accountToRename.name));
+                resultAccount = renamedAccount;
 
-            int parentUserId = accounts.userId;
-            if (canHaveProfile(parentUserId)) {
+                int parentUserId = accounts.userId;
+                if (canHaveProfile(parentUserId)) {
                 /*
                  * Owner or system user account was renamed, rename the account for
                  * those users with which the account was shared.
                  */
-                List<UserInfo> users = getUserManager().getUsers(true);
-                for (UserInfo user : users) {
-                    if (user.isRestricted()
-                            && (user.restrictedProfileParentId == parentUserId)) {
-                        renameSharedAccountAsUser(accountToRename, newName, user.id);
+                    List<UserInfo> users = getUserManager().getUsers(true);
+                    for (UserInfo user : users) {
+                        if (user.isRestricted()
+                                && (user.restrictedProfileParentId == parentUserId)) {
+                            renameSharedAccountAsUser(accountToRename, newName, user.id);
+                        }
                     }
                 }
-            }
 
-            sendNotificationAccountUpdated(resultAccount, accounts);
-            sendAccountsChangedBroadcast(accounts.userId);
+                sendNotificationAccountUpdated(resultAccount, accounts);
+                sendAccountsChangedBroadcast(accounts.userId);
+            }
         }
         return resultAccount;
     }
@@ -2075,42 +2150,47 @@
             Slog.i(TAG, "Removing account " + account + " while user "+ accounts.userId
                     + " is still locked. CE data will be removed later");
         }
-        synchronized (accounts.cacheLock) {
-            Map<String, Integer> packagesToVisibility = getRequestingPackages(account, accounts);
-            accounts.accountsDb.beginTransaction();
-            // Set to a dummy value, this will only be used if the database
-            // transaction succeeds.
-            long accountId = -1;
-            try {
-                accountId = accounts.accountsDb.findDeAccountId(account);
-                if (accountId >= 0) {
-                    isChanged = accounts.accountsDb.deleteDeAccount(accountId);
-                }
-                // always delete from CE table if CE storage is available
-                // DE account could be removed while CE was locked
-                if (userUnlocked) {
-                    long ceAccountId = accounts.accountsDb.findCeAccountId(account);
-                    if (ceAccountId >= 0) {
-                        accounts.accountsDb.deleteCeAccount(ceAccountId);
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                Map<String, Integer> packagesToVisibility = getRequestingPackages(account,
+                        accounts);
+                accounts.accountsDb.beginTransaction();
+                // Set to a dummy value, this will only be used if the database
+                // transaction succeeds.
+                long accountId = -1;
+                try {
+                    accountId = accounts.accountsDb.findDeAccountId(account);
+                    if (accountId >= 0) {
+                        isChanged = accounts.accountsDb.deleteDeAccount(accountId);
                     }
-                }
-                accounts.accountsDb.setTransactionSuccessful();
-            } finally {
-                accounts.accountsDb.endTransaction();
-            }
-            if (isChanged) {
-                removeAccountFromCacheLocked(accounts, account);
-                for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
-                    if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
-                        notifyPackage(packageToVisibility.getKey(), accounts);
+                    // always delete from CE table if CE storage is available
+                    // DE account could be removed while CE was locked
+                    if (userUnlocked) {
+                        long ceAccountId = accounts.accountsDb.findCeAccountId(account);
+                        if (ceAccountId >= 0) {
+                            accounts.accountsDb.deleteCeAccount(ceAccountId);
+                        }
                     }
+                    accounts.accountsDb.setTransactionSuccessful();
+                } finally {
+                    accounts.accountsDb.endTransaction();
                 }
+                if (isChanged) {
+                    removeAccountFromCacheLocked(accounts, account);
+                    for (Entry<String, Integer> packageToVisibility : packagesToVisibility
+                            .entrySet()) {
+                        if (packageToVisibility.getValue()
+                                != AccountManager.VISIBILITY_NOT_VISIBLE) {
+                            notifyPackage(packageToVisibility.getKey(), accounts);
+                        }
+                    }
 
-                // Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occurred.
-                sendAccountsChangedBroadcast(accounts.userId);
-                String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
-                        : AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
-                logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
+                    // Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occurred.
+                    sendAccountsChangedBroadcast(accounts.userId);
+                    String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
+                            : AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
+                    logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
+                }
             }
         }
         long id = Binder.clearCallingIdentity();
@@ -2160,14 +2240,16 @@
         long identityToken = clearCallingIdentity();
         try {
             UserAccounts accounts = getUserAccounts(userId);
-            synchronized (accounts.cacheLock) {
-                accounts.accountsDb.beginTransaction();
-                try {
-                    invalidateAuthTokenLocked(accounts, accountType, authToken);
-                    invalidateCustomTokenLocked(accounts, accountType, authToken);
-                    accounts.accountsDb.setTransactionSuccessful();
-                } finally {
-                    accounts.accountsDb.endTransaction();
+            synchronized (accounts.dbLock) {
+                synchronized (accounts.cacheLock) {
+                    accounts.accountsDb.beginTransaction();
+                    try {
+                        invalidateAuthTokenLocked(accounts, accountType, authToken);
+                        invalidateCustomTokenLocked(accounts, accountType, authToken);
+                        accounts.accountsDb.setTransactionSuccessful();
+                    } finally {
+                        accounts.accountsDb.endTransaction();
+                    }
                 }
             }
         } finally {
@@ -2223,9 +2305,11 @@
         }
         cancelNotification(getSigninRequiredNotificationId(accounts, account),
                 UserHandle.of(accounts.userId));
-        synchronized (accounts.cacheLock) {
-            accounts.accountTokenCaches.put(
-                    account, token, tokenType, callerPkg, callerSigDigest, expiryMillis);
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                accounts.accountTokenCaches.put(
+                        account, token, tokenType, callerPkg, callerSigDigest, expiryMillis);
+            }
         }
     }
 
@@ -2236,22 +2320,24 @@
         }
         cancelNotification(getSigninRequiredNotificationId(accounts, account),
                 UserHandle.of(accounts.userId));
-        synchronized (accounts.cacheLock) {
-            accounts.accountsDb.beginTransaction();
-            try {
-                long accountId = accounts.accountsDb.findDeAccountId(account);
-                if (accountId < 0) {
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                accounts.accountsDb.beginTransaction();
+                try {
+                    long accountId = accounts.accountsDb.findDeAccountId(account);
+                    if (accountId < 0) {
+                        return false;
+                    }
+                    accounts.accountsDb.deleteAuthtokensByAccountIdAndType(accountId, type);
+                    if (accounts.accountsDb.insertAuthToken(accountId, type, authToken) >= 0) {
+                        accounts.accountsDb.setTransactionSuccessful();
+                        writeAuthTokenIntoCacheLocked(accounts, account, type, authToken);
+                        return true;
+                    }
                     return false;
+                } finally {
+                    accounts.accountsDb.endTransaction();
                 }
-                accounts.accountsDb.deleteAuthtokensByAccountIdAndType(accountId, type);
-                if (accounts.accountsDb.insertAuthToken(accountId, type, authToken) >= 0) {
-                    accounts.accountsDb.setTransactionSuccessful();
-                    writeAuthTokenIntoCacheLocked(accounts, account, type, authToken);
-                    return true;
-                }
-                return false;
-            } finally {
-                accounts.accountsDb.endTransaction();
             }
         }
     }
@@ -2349,31 +2435,35 @@
             return;
         }
         boolean isChanged = false;
-        synchronized (accounts.cacheLock) {
-            accounts.accountsDb.beginTransaction();
-            try {
-                final long accountId = accounts.accountsDb.findDeAccountId(account);
-                if (accountId >= 0) {
-                    accounts.accountsDb.updateCeAccountPassword(accountId, password);
-                    accounts.accountsDb.deleteAuthTokensByAccountId(accountId);
-                    accounts.authTokenCache.remove(account);
-                    accounts.accountTokenCaches.remove(account);
-                    accounts.accountsDb.setTransactionSuccessful();
-                    // If there is an account whose password will be updated and the database
-                    // transactions succeed, then we say that a change has occured. Even if the
-                    // new password is the same as the old and there were no authtokens to delete.
-                    isChanged = true;
-                    String action = (password == null || password.length() == 0) ?
-                            AccountsDb.DEBUG_ACTION_CLEAR_PASSWORD
-                            : AccountsDb.DEBUG_ACTION_SET_PASSWORD;
-                    logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts, callingUid);
-                }
-            } finally {
-                accounts.accountsDb.endTransaction();
-                if (isChanged) {
-                    // Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
-                    sendNotificationAccountUpdated(account, accounts);
-                    sendAccountsChangedBroadcast(accounts.userId);
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                accounts.accountsDb.beginTransaction();
+                try {
+                    final long accountId = accounts.accountsDb.findDeAccountId(account);
+                    if (accountId >= 0) {
+                        accounts.accountsDb.updateCeAccountPassword(accountId, password);
+                        accounts.accountsDb.deleteAuthTokensByAccountId(accountId);
+                        accounts.authTokenCache.remove(account);
+                        accounts.accountTokenCaches.remove(account);
+                        accounts.accountsDb.setTransactionSuccessful();
+                        // If there is an account whose password will be updated and the database
+                        // transactions succeed, then we say that a change has occured. Even if the
+                        // new password is the same as the old and there were no authtokens to
+                        // delete.
+                        isChanged = true;
+                        String action = (password == null || password.length() == 0) ?
+                                AccountsDb.DEBUG_ACTION_CLEAR_PASSWORD
+                                : AccountsDb.DEBUG_ACTION_SET_PASSWORD;
+                        logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts,
+                                callingUid);
+                    }
+                } finally {
+                    accounts.accountsDb.endTransaction();
+                    if (isChanged) {
+                        // Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
+                        sendNotificationAccountUpdated(account, accounts);
+                        sendAccountsChangedBroadcast(accounts.userId);
+                    }
                 }
             }
         }
@@ -2427,11 +2517,13 @@
         long identityToken = clearCallingIdentity();
         try {
             UserAccounts accounts = getUserAccounts(userId);
-            synchronized (accounts.cacheLock) {
-                if (!accountExistsCacheLocked(accounts, account)) {
-                    return;
+            synchronized (accounts.dbLock) {
+                synchronized (accounts.cacheLock) {
+                    if (!accountExistsCacheLocked(accounts, account)) {
+                        return;
+                    }
+                    setUserdataInternalLocked(accounts, account, key, value);
                 }
-                setUserdataInternalLocked(accounts, account, key, value);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -3855,9 +3947,12 @@
 
         @Override
         public void run() throws RemoteException {
-            synchronized (mAccounts.cacheLock) {
-                mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid,
-                        mPackageName, false /* include managed not visible*/);
+            synchronized (mAccounts.dbLock) {
+                synchronized (mAccounts.cacheLock) {
+                    mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType,
+                            mCallingUid,
+                            mPackageName, false /* include managed not visible*/);
+                }
             }
             // check whether each account matches the requested features
             mAccountsWithFeatures = new ArrayList<>(mAccountsOfType.length);
@@ -4002,15 +4097,17 @@
         for (int userId : userIds) {
             UserAccounts userAccounts = getUserAccounts(userId);
             if (userAccounts == null) continue;
-            synchronized (userAccounts.cacheLock) {
-                Account[] accounts = getAccountsFromCacheLocked(
-                        userAccounts,
-                        null /* type */,
-                        Binder.getCallingUid(),
-                        null /* packageName */,
-                        false /* include managed not visible*/);
-                for (int a = 0; a < accounts.length; a++) {
-                    runningAccounts.add(new AccountAndUser(accounts[a], userId));
+            synchronized (userAccounts.dbLock) {
+                synchronized (userAccounts.cacheLock) {
+                    Account[] accounts = getAccountsFromCacheLocked(
+                            userAccounts,
+                            null /* type */,
+                            Binder.getCallingUid(),
+                            null /* packageName */,
+                            false /* include managed not visible*/);
+                    for (int a = 0; a < accounts.length; a++) {
+                        runningAccounts.add(new AccountAndUser(accounts[a], userId));
+                    }
                 }
             }
         }
@@ -4097,21 +4194,23 @@
             String callingPackage,
             List<String> visibleAccountTypes,
             boolean includeUserManagedNotVisible) {
-        synchronized (userAccounts.cacheLock) {
-            ArrayList<Account> visibleAccounts = new ArrayList<>();
-            for (String visibleType : visibleAccountTypes) {
-                Account[] accountsForType = getAccountsFromCacheLocked(
-                        userAccounts, visibleType, callingUid, callingPackage,
-                        includeUserManagedNotVisible);
-                if (accountsForType != null) {
-                    visibleAccounts.addAll(Arrays.asList(accountsForType));
+        synchronized (userAccounts.dbLock) {
+            synchronized (userAccounts.cacheLock) {
+                ArrayList<Account> visibleAccounts = new ArrayList<>();
+                for (String visibleType : visibleAccountTypes) {
+                    Account[] accountsForType = getAccountsFromCacheLocked(
+                            userAccounts, visibleType, callingUid, callingPackage,
+                            includeUserManagedNotVisible);
+                    if (accountsForType != null) {
+                        visibleAccounts.addAll(Arrays.asList(accountsForType));
+                    }
                 }
+                Account[] result = new Account[visibleAccounts.size()];
+                for (int i = 0; i < visibleAccounts.size(); i++) {
+                    result[i] = visibleAccounts.get(i);
+                }
+                return result;
             }
-            Account[] result = new Account[visibleAccounts.size()];
-            for (int i = 0; i < visibleAccounts.size(); i++) {
-                result[i] = visibleAccounts.get(i);
-            }
-            return result;
         }
     }
 
@@ -4262,9 +4361,11 @@
             UserAccounts userAccounts = getUserAccounts(userId);
             if (features == null || features.length == 0) {
                 Account[] accounts;
-                synchronized (userAccounts.cacheLock) {
-                    accounts = getAccountsFromCacheLocked(
-                            userAccounts, type, callingUid, opPackageName, false);
+                synchronized (userAccounts.dbLock) {
+                    synchronized (userAccounts.cacheLock) {
+                        accounts = getAccountsFromCacheLocked(
+                                userAccounts, type, callingUid, opPackageName, false);
+                    }
                 }
                 Bundle result = new Bundle();
                 result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
@@ -4821,32 +4922,35 @@
 
     private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout,
             String[] args, boolean isCheckinRequest) {
-        synchronized (userAccounts.cacheLock) {
-            if (isCheckinRequest) {
-                // This is a checkin request. *Only* upload the account types and the count of each.
-                userAccounts.accountsDb.dumpDeAccountsTable(fout);
-            } else {
-                Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
-                        Process.SYSTEM_UID, null /* packageName */, false);
-                fout.println("Accounts: " + accounts.length);
-                for (Account account : accounts) {
-                    fout.println("  " + account);
-                }
-
-                // Add debug information.
-                fout.println();
-                userAccounts.accountsDb.dumpDebugTable(fout);
-                fout.println();
-                synchronized (mSessions) {
-                    final long now = SystemClock.elapsedRealtime();
-                    fout.println("Active Sessions: " + mSessions.size());
-                    for (Session session : mSessions.values()) {
-                        fout.println("  " + session.toDebugString(now));
+        synchronized (userAccounts.dbLock) {
+            synchronized (userAccounts.cacheLock) {
+                if (isCheckinRequest) {
+                    // This is a checkin request. *Only* upload the account types and the count of
+                    // each.
+                    userAccounts.accountsDb.dumpDeAccountsTable(fout);
+                } else {
+                    Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
+                            Process.SYSTEM_UID, null /* packageName */, false);
+                    fout.println("Accounts: " + accounts.length);
+                    for (Account account : accounts) {
+                        fout.println("  " + account);
                     }
-                }
 
-                fout.println();
-                mAuthenticatorCache.dump(fd, fout, args, userAccounts.userId);
+                    // Add debug information.
+                    fout.println();
+                    userAccounts.accountsDb.dumpDebugTable(fout);
+                    fout.println();
+                    synchronized (mSessions) {
+                        final long now = SystemClock.elapsedRealtime();
+                        fout.println("Active Sessions: " + mSessions.size());
+                        for (Session session : mSessions.values()) {
+                            fout.println("  " + session.toDebugString(now));
+                        }
+                    }
+
+                    fout.println();
+                    mAuthenticatorCache.dump(fd, fout, args, userAccounts.userId);
+                }
             }
         }
     }
@@ -5160,26 +5264,28 @@
             return true;
         }
         UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callerUid));
-        synchronized (accounts.cacheLock) {
-            long grantsCount;
-            if (authTokenType != null) {
-                grantsCount = accounts.accountsDb.findMatchingGrantsCount(callerUid, authTokenType,
-                        account);
-            } else {
-                grantsCount = accounts.accountsDb.findMatchingGrantsCountAnyToken(callerUid,
-                        account);
-            }
-            final boolean permissionGranted = grantsCount > 0;
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                long grantsCount;
+                if (authTokenType != null) {
+                    grantsCount = accounts.accountsDb
+                            .findMatchingGrantsCount(callerUid, authTokenType, account);
+                } else {
+                    grantsCount = accounts.accountsDb.findMatchingGrantsCountAnyToken(callerUid,
+                            account);
+                }
+                final boolean permissionGranted = grantsCount > 0;
 
-            if (!permissionGranted && ActivityManager.isRunningInTestHarness()) {
-                // TODO: Skip this check when running automated tests. Replace this
-                // with a more general solution.
-                Log.d(TAG, "no credentials permission for usage of " + account + ", "
-                        + authTokenType + " by uid " + callerUid
-                        + " but ignoring since device is in test harness.");
-                return true;
+                if (!permissionGranted && ActivityManager.isRunningInTestHarness()) {
+                    // TODO: Skip this check when running automated tests. Replace this
+                    // with a more general solution.
+                    Log.d(TAG, "no credentials permission for usage of " + account + ", "
+                            + authTokenType + " by uid " + callerUid
+                            + " but ignoring since device is in test harness.");
+                    return true;
+                }
+                return permissionGranted;
             }
-            return permissionGranted;
         }
     }
 
@@ -5293,15 +5399,18 @@
             return;
         }
         UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
-        synchronized (accounts.cacheLock) {
-            long accountId = accounts.accountsDb.findDeAccountId(account);
-            if (accountId >= 0) {
-                accounts.accountsDb.insertGrant(accountId, authTokenType, uid);
-            }
-            cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
-                    UserHandle.of(accounts.userId));
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                long accountId = accounts.accountsDb.findDeAccountId(account);
+                if (accountId >= 0) {
+                    accounts.accountsDb.insertGrant(accountId, authTokenType, uid);
+                }
+                cancelNotification(
+                        getCredentialPermissionNotificationId(account, authTokenType, uid),
+                        UserHandle.of(accounts.userId));
 
-            cancelAccountAccessRequestNotificationIfNeeded(account, uid, true);
+                cancelAccountAccessRequestNotificationIfNeeded(account, uid, true);
+            }
         }
 
         // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
@@ -5325,21 +5434,24 @@
             return;
         }
         UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
-        synchronized (accounts.cacheLock) {
-            accounts.accountsDb.beginTransaction();
-            try {
-                long accountId = accounts.accountsDb.findDeAccountId(account);
-                if (accountId >= 0) {
-                    accounts.accountsDb.deleteGrantsByAccountIdAuthTokenTypeAndUid(
-                            accountId, authTokenType, uid);
-                    accounts.accountsDb.setTransactionSuccessful();
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                accounts.accountsDb.beginTransaction();
+                try {
+                    long accountId = accounts.accountsDb.findDeAccountId(account);
+                    if (accountId >= 0) {
+                        accounts.accountsDb.deleteGrantsByAccountIdAuthTokenTypeAndUid(
+                                accountId, authTokenType, uid);
+                        accounts.accountsDb.setTransactionSuccessful();
+                    }
+                } finally {
+                    accounts.accountsDb.endTransaction();
                 }
-            } finally {
-                accounts.accountsDb.endTransaction();
-            }
 
-            cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
-                    new UserHandle(accounts.userId));
+                cancelNotification(
+                        getCredentialPermissionNotificationId(account, authTokenType, uid),
+                        UserHandle.of(accounts.userId));
+            }
         }
 
         // Listeners are a final CopyOnWriteArrayList, hence no lock needed.
@@ -5369,6 +5481,7 @@
         accounts.userDataCache.remove(account);
         accounts.authTokenCache.remove(account);
         accounts.previousNameCache.remove(account);
+        accounts.visibilityCache.remove(account);
     }
 
     /**
@@ -5548,9 +5661,11 @@
             String tokenType,
             String callingPackage,
             byte[] pkgSigDigest) {
-        synchronized (accounts.cacheLock) {
-            return accounts.accountTokenCaches.get(
-                    account, tokenType, callingPackage, pkgSigDigest);
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                return accounts.accountTokenCaches.get(
+                        account, tokenType, callingPackage, pkgSigDigest);
+            }
         }
     }
 
@@ -5570,14 +5685,16 @@
 
     protected String readAuthTokenInternal(UserAccounts accounts, Account account,
             String authTokenType) {
-        synchronized (accounts.cacheLock) {
-            Map<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
-            if (authTokensForAccount == null) {
-                // need to populate the cache for this account
-                authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account);
-                accounts.authTokenCache.put(account, authTokensForAccount);
+        synchronized (accounts.dbLock) {
+            synchronized (accounts.cacheLock) {
+                Map<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
+                if (authTokensForAccount == null) {
+                    // need to populate the cache for this account
+                    authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account);
+                    accounts.authTokenCache.put(account, authTokensForAccount);
+                }
+                return authTokensForAccount.get(authTokenType);
             }
-            return authTokensForAccount.get(authTokenType);
         }
     }
 
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 272fbf8..7a83436 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2743,6 +2743,7 @@
     @VisibleForTesting
     public ActivityManagerService(Injector injector) {
         mInjector = injector;
+        mContext = mInjector.getContext();
         GL_ES_VERSION = 0;
         mActivityStarter = null;
         mAppErrors = null;
@@ -23875,6 +23876,10 @@
     public static class Injector {
         private NetworkManagementInternal mNmi;
 
+        public Context getContext() {
+            return null;
+        }
+
         public AppOpsService getAppOpsService(File file, Handler handler) {
             return new AppOpsService(file, handler);
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index aaad12c..9b6d13a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -82,6 +82,8 @@
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+
 final class ActivityManagerShellCommand extends ShellCommand {
     public static final String NO_CLASS_ERROR_CODE = "Error type 3";
     private static final String SHELL_PACKAGE_NAME = "com.android.shell";
@@ -118,6 +120,8 @@
     private boolean mStreaming;   // Streaming the profiling output to a file.
     private int mDisplayId;
     private int mStackId;
+    private int mTaskId;
+    private boolean mIsTaskOverlay;
 
     final boolean mDumping;
 
@@ -263,6 +267,8 @@
         mUserId = defUser;
         mDisplayId = INVALID_DISPLAY;
         mStackId = INVALID_STACK_ID;
+        mTaskId = INVALID_TASK_ID;
+        mIsTaskOverlay = false;
 
         return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
             @Override
@@ -297,6 +303,10 @@
                     mDisplayId = Integer.parseInt(getNextArgRequired());
                 } else if (opt.equals("--stack")) {
                     mStackId = Integer.parseInt(getNextArgRequired());
+                } else if (opt.equals("--task")) {
+                    mTaskId = Integer.parseInt(getNextArgRequired());
+                } else if (opt.equals("--task-overlay")) {
+                    mIsTaskOverlay = true;
                 } else {
                     return false;
                 }
@@ -380,6 +390,14 @@
                 options = ActivityOptions.makeBasic();
                 options.setLaunchStackId(mStackId);
             }
+            if (mTaskId != INVALID_TASK_ID) {
+                options = ActivityOptions.makeBasic();
+                options.setLaunchTaskId(mTaskId);
+
+                if (mIsTaskOverlay) {
+                    options.setTaskOverlay(true, true /* canResume */);
+                }
+            }
             if (mWaitOption) {
                 result = mInterface.startActivityAndWait(null, null, intent, mimeType,
                         null, null, 0, mStartFlags, profilerInfo,
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 28a4e1a..498de63 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -67,7 +67,9 @@
 import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
+import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
 import static com.android.server.wm.AppTransition.TRANSIT_NONE;
@@ -121,7 +123,6 @@
 import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
 import com.android.server.wm.StackWindowController;
 import com.android.server.wm.StackWindowListener;
-import com.android.server.wm.TaskStack;
 import com.android.server.wm.WindowManagerService;
 
 import java.io.FileDescriptor;
@@ -1185,13 +1186,13 @@
      * @param resuming The activity we are currently trying to resume or null if this is not being
      *                 called as part of resuming the top activity, so we shouldn't try to instigate
      *                 a resume here if not null.
-     * @param dontWait True if the caller does not want to wait for the pause to complete.  If
-     * set to true, we will immediately complete the pause here before returning.
+     * @param pauseImmediately True if the caller does not want to wait for the activity callback to
+     *                         complete pausing.
      * @return Returns true if an activity now is in the PAUSING state, and we are waiting for
      * it to tell us when it is done.
      */
     final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
-            ActivityRecord resuming, boolean dontWait) {
+            ActivityRecord resuming, boolean pauseImmediately) {
         if (mPausingActivity != null) {
             Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
                     + " state=" + mPausingActivity.state);
@@ -1213,7 +1214,8 @@
 
         if (mActivityContainer.mParentActivity == null) {
             // Top level stack, not a child. Look for child stacks.
-            mStackSupervisor.pauseChildStacks(prev, userLeaving, uiSleeping, resuming, dontWait);
+            mStackSupervisor.pauseChildStacks(prev, userLeaving, uiSleeping, resuming,
+                    pauseImmediately);
         }
 
         if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSING: " + prev);
@@ -1243,7 +1245,7 @@
                         prev.shortComponentName);
                 mService.updateUsageStats(prev, false);
                 prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
-                        userLeaving, prev.configChangeFlags, dontWait);
+                        userLeaving, prev.configChangeFlags, pauseImmediately);
             } catch (Exception e) {
                 // Ignore exception, if process died other code will cleanup.
                 Slog.w(TAG, "Exception thrown during pause", e);
@@ -1274,7 +1276,7 @@
                  Slog.v(TAG_PAUSE, "Key dispatch not paused for screen off");
             }
 
-            if (dontWait) {
+            if (pauseImmediately) {
                 // If the caller said they don't want to wait for the pause, then complete
                 // the pause now.
                 completePauseLocked(false, resuming);
@@ -3488,11 +3490,19 @@
     }
 
     /**
+     * See {@link #finishActivityLocked(ActivityRecord, int, Intent, String, boolean, boolean)}
+     */
+    final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,
+            String reason, boolean oomAdj) {
+        return finishActivityLocked(r, resultCode, resultData, reason, oomAdj, !PAUSE_IMMEDIATELY);
+    }
+
+    /**
      * @return Returns true if this activity has been removed from the history
      * list, or false if it is still in the list and will be removed later.
      */
     final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,
-            String reason, boolean oomAdj) {
+            String reason, boolean oomAdj, boolean pauseImmediately) {
         if (r.finishing) {
             Slog.w(TAG, "Duplicate finish request for " + r);
             return false;
@@ -3529,9 +3539,10 @@
             if (mResumedActivity == r) {
                 if (DEBUG_VISIBILITY || DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
                         "Prepare close transition: finishing " + r);
-            if (endTask) {
-                mService.mTaskChangeNotificationController.notifyTaskRemovalStarted(task.taskId);
-            }
+                if (endTask) {
+                    mService.mTaskChangeNotificationController.notifyTaskRemovalStarted(
+                            task.taskId);
+                }
                 mWindowManager.prepareAppTransition(transit, false);
 
                 // Tell window manager to prepare for this one to be removed.
@@ -3541,7 +3552,7 @@
                     if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish needs to pause: " + r);
                     if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
                             "finish() => pause with userLeaving=false");
-                    startPausingLocked(false, false, null, false);
+                    startPausingLocked(false, false, null, pauseImmediately);
                 }
 
                 if (endTask) {
@@ -3552,15 +3563,30 @@
                 // it is done pausing; else we can just directly finish it here.
                 if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + r);
                 if (r.visible) {
-                    mWindowManager.prepareAppTransition(transit, false);
-                    r.setVisibility(false);
-                    mWindowManager.executeAppTransition();
-                    if (!mStackSupervisor.mWaitingVisibleActivities.contains(r)) {
-                        mStackSupervisor.mWaitingVisibleActivities.add(r);
+                    prepareActivityHideTransitionAnimation(r, transit);
+                }
+
+                final int finishMode = (r.visible || r.nowVisible) ? FINISH_AFTER_VISIBLE
+                        : FINISH_AFTER_PAUSE;
+                final boolean removedActivity = finishCurrentActivityLocked(r, finishMode, oomAdj)
+                        == null;
+
+                // The following code is an optimization. When the last non-task overlay activity
+                // is removed from the task, we remove the entire task from the stack. However,
+                // since that is done after the scheduled destroy callback from the activity, that
+                // call to change the visibility of the task overlay activities would be out of
+                // sync with the activitiy visibility being set for this finishing activity above.
+                // In this case, we can set the visibility of all the task overlay activities when
+                // we detect the last one is finishing to keep them in sync.
+                if (task.onlyHasTaskOverlayActivities(true /* excludeFinishing */)) {
+                    for (ActivityRecord taskOverlay : task.mActivities) {
+                        if (!taskOverlay.mTaskOverlay) {
+                            continue;
+                        }
+                        prepareActivityHideTransitionAnimation(taskOverlay, transit);
                     }
                 }
-                return finishCurrentActivityLocked(r, (r.visible || r.nowVisible) ?
-                        FINISH_AFTER_VISIBLE : FINISH_AFTER_PAUSE, oomAdj) == null;
+                return removedActivity;
             } else {
                 if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + r);
             }
@@ -3571,6 +3597,15 @@
         }
     }
 
+    private void prepareActivityHideTransitionAnimation(ActivityRecord r, int transit) {
+        mWindowManager.prepareAppTransition(transit, false);
+        r.setVisibility(false);
+        mWindowManager.executeAppTransition();
+        if (!mStackSupervisor.mWaitingVisibleActivities.contains(r)) {
+            mStackSupervisor.mWaitingVisibleActivities.add(r);
+        }
+    }
+
     static final int FINISH_IMMEDIATELY = 0;
     static final int FINISH_AFTER_PAUSE = 1;
     static final int FINISH_AFTER_VISIBLE = 2;
@@ -3864,15 +3899,23 @@
         r.removeWindowContainer();
         final TaskRecord task = r.task;
         final boolean lastActivity = task != null ? task.removeActivity(r) : false;
+        // If we are removing the last activity in the task, not including task overlay activities,
+        // then fall through into the block below to remove the entire task itself
+        final boolean onlyHasTaskOverlays = task != null
+                ? task.onlyHasTaskOverlayActivities(false /* excludingFinishing */) : false;
 
-        if (lastActivity) {
-            if (DEBUG_STACK) Slog.i(TAG_STACK,
-                    "removeActivityFromHistoryLocked: last activity removed from " + this);
+        if (lastActivity || onlyHasTaskOverlays) {
+            if (DEBUG_STACK) {
+                Slog.i(TAG_STACK,
+                        "removeActivityFromHistoryLocked: last activity removed from " + this
+                                + " onlyHasTaskOverlays=" + onlyHasTaskOverlays);
+            }
+
             if (mStackSupervisor.isFocusedStack(this) && task == topTask() &&
                     task.isOverHomeStack()) {
                 mStackSupervisor.moveHomeStackTaskToTop(reason);
             }
-            removeTask(task, reason);
+            removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING);
         }
         cleanUpActivityServicesLocked(r);
         r.removeUriPermissionsLocked();
@@ -4923,10 +4966,6 @@
         return starting;
     }
 
-    void removeTask(TaskRecord task, String reason) {
-        removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING);
-    }
-
     /**
      * Removes the input task from this stack.
      * @param task to remove.
@@ -4936,6 +4975,10 @@
      */
     void removeTask(TaskRecord task, String reason, int mode) {
         if (mode == REMOVE_TASK_MODE_DESTROYING) {
+            // When destroying a task, tell the supervisor to remove it so that any activity it has
+            // can be cleaned up correctly
+            mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
+                    !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY);
             task.removeWindowContainer();
         }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 8559dca..b623b2f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -254,6 +254,10 @@
     // Used to indicate that a task is removed it should also be removed from recents.
     static final boolean REMOVE_FROM_RECENTS = true;
 
+    // Used to indicate that pausing an activity should occur immediately without waiting for
+    // the activity callback indicating that it has completed pausing
+    static final boolean PAUSE_IMMEDIATELY = true;
+
     /**
      * The modes which affect which tasks are returned when calling
      * {@link ActivityStackSupervisor#anyTaskForIdLocked(int)}.
@@ -2536,18 +2540,28 @@
     }
 
     /**
+     * See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)}
+     */
+    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
+        return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY);
+    }
+
+    /**
      * Removes the task with the specified task id.
      *
      * @param taskId Identifier of the task to be removed.
      * @param killProcess Kill any process associated with the task if possible.
      * @param removeFromRecents Whether to also remove the task from recents.
+     * @param pauseImmediately Pauses all task activities immediately without waiting for the
+     *                         pause-complete callback from the activity.
      * @return Returns true if the given task was found and removed.
      */
-    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
+    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
+            boolean pauseImmediately) {
         final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
                 INVALID_STACK_ID);
         if (tr != null) {
-            tr.removeTaskActivitiesLocked();
+            tr.removeTaskActivitiesLocked(pauseImmediately);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
             if (tr.isPersistable) {
                 mService.notifyTaskPersisterLocked(null, true);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 7b1af38..c10f77c 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -870,7 +870,7 @@
             nativeProcs = NATIVE_STACKS_OF_INTEREST;
         }
 
-        int[] pids = Process.getPidsForCommands(nativeProcs);
+        int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
         ArrayList<Integer> nativePids = null;
 
         if (pids != null) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index bff5f51..ce32f84 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -112,6 +112,7 @@
 import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
+import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 
 import static java.lang.Integer.MAX_VALUE;
@@ -1280,6 +1281,26 @@
         return false;
     }
 
+    /**
+     * @return whether or not there are ONLY task overlay activities in the stack.
+     *         If {@param excludeFinishing} is set, then ignore finishing activities in the check.
+     *         If there are no task overlay activities, this call returns false.
+     */
+    boolean onlyHasTaskOverlayActivities(boolean excludeFinishing) {
+        int count = 0;
+        for (int i = mActivities.size() - 1; i >= 0; i--) {
+            final ActivityRecord r = mActivities.get(i);
+            if (excludeFinishing && r.finishing) {
+                continue;
+            }
+            if (!r.mTaskOverlay) {
+                return false;
+            }
+            count++;
+        }
+        return count > 0;
+    }
+
     boolean autoRemoveFromRecents() {
         // We will automatically remove the task either if it has explicitly asked for
         // this, or it is empty and has never contained an activity that got shown to
@@ -1291,7 +1312,7 @@
      * Completely remove all activities associated with an existing
      * task starting at a specified index.
      */
-    final void performClearTaskAtIndexLocked(int activityNdx) {
+    final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately) {
         int numActivities = mActivities.size();
         for ( ; activityNdx < numActivities; ++activityNdx) {
             final ActivityRecord r = mActivities.get(activityNdx);
@@ -1304,8 +1325,8 @@
                 mActivities.remove(activityNdx);
                 --activityNdx;
                 --numActivities;
-            } else if (mStack.finishActivityLocked(
-                    r, Activity.RESULT_CANCELED, null, "clear-task-index", false)) {
+            } else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null,
+                    "clear-task-index", false, pauseImmediately)) {
                 --activityNdx;
                 --numActivities;
             }
@@ -1317,7 +1338,7 @@
      */
     final void performClearTaskLocked() {
         mReuseTask = true;
-        performClearTaskAtIndexLocked(0);
+        performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY);
         mReuseTask = false;
     }
 
@@ -1401,9 +1422,9 @@
         return taskThumbnail;
     }
 
-    void removeTaskActivitiesLocked() {
+    void removeTaskActivitiesLocked(boolean pauseImmediately) {
         // Just remove the entire task.
-        performClearTaskAtIndexLocked(0);
+        performClearTaskAtIndexLocked(0, pauseImmediately);
     }
 
     String lockTaskAuthToString() {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e08ab60..49d1521 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6598,7 +6598,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);
     }
@@ -6609,7 +6609,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/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/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java
index ebbb8b3..42f12eb 100644
--- a/services/core/java/com/android/server/tv/TvInputHal.java
+++ b/services/core/java/com/android/server/tv/TvInputHal.java
@@ -16,6 +16,7 @@
 
 package com.android.server.tv;
 
+import android.hardware.tv.input.V1_0.Constants;
 import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvStreamConfig;
 import android.os.Handler;
@@ -41,9 +42,10 @@
     public final static int ERROR_STALE_CONFIG = -2;
     public final static int ERROR_UNKNOWN = -3;
 
-    public static final int EVENT_DEVICE_AVAILABLE = 1;
-    public static final int EVENT_DEVICE_UNAVAILABLE = 2;
-    public static final int EVENT_STREAM_CONFIGURATION_CHANGED = 3;
+    public static final int EVENT_DEVICE_AVAILABLE = Constants.EVENT_DEVICE_AVAILABLE;
+    public static final int EVENT_DEVICE_UNAVAILABLE = Constants.EVENT_DEVICE_UNAVAILABLE;
+    public static final int EVENT_STREAM_CONFIGURATION_CHANGED =
+            Constants.EVENT_STREAM_CONFIGURATIONS_CHANGED;
     public static final int EVENT_FIRST_FRAME_CAPTURED = 4;
 
     public interface Callback {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 72ae90d..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;
@@ -1204,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/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 2a02359..84ba139 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -516,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;
@@ -537,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/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index b433350..78c0fa7 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -577,7 +577,6 @@
 }
 
 Return<void> JTvInputHal::TvInputCallback::notify(const TvInputEvent& event) {
-    // TODO(b/32200867): Ensure the event type values are in sync with the framework code.
     mHal->mLooper->sendMessage(new NotifyHandler(mHal, event), static_cast<int>(event.type));
     return Void();
 }
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 7b4fa87..590bce1 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -608,7 +608,7 @@
     }
 
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        if (args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
             // Execute confirmConfiguration() and take no further action.
             confirmConfiguration();
             return;
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 2a8f4a3..bb4507d 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -52,6 +52,7 @@
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
 LOCAL_JACK_FLAGS := --multi-dex native
+LOCAL_DX_FLAGS := --multi-dex
 
 LOCAL_STATIC_JAVA_LIBRARIES += ub-uiautomator
 
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index a9c69f6..e0ac393 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -128,9 +128,11 @@
     private static final ScoredNetwork SCORED_NETWORK_2 =
             new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID_2), "00:00:00:00:00:00")),
                     null /* rssiCurve*/);
+    private static final String NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID =
+            "networkAvailableNotificationChannelId";
     private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData(
             1, RECOMMENDATION_SERVICE_COMP, RECOMMENDATION_SERVICE_LABEL,
-            USE_WIFI_ENABLE_ACTIVITY_COMP);
+            USE_WIFI_ENABLE_ACTIVITY_COMP, NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID);
 
     @Mock private NetworkScorerAppManager mNetworkScorerAppManager;
     @Mock private Context mContext;
@@ -965,7 +967,7 @@
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
         NetworkScorerAppData expectedAppData = new NetworkScorerAppData(Binder.getCallingUid(),
                 RECOMMENDATION_SERVICE_COMP, RECOMMENDATION_SERVICE_LABEL,
-                USE_WIFI_ENABLE_ACTIVITY_COMP);
+                USE_WIFI_ENABLE_ACTIVITY_COMP, NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID);
         bindToScorer(expectedAppData);
         assertEquals(expectedAppData, mNetworkScoreService.getActiveScorer());
     }
@@ -1007,7 +1009,7 @@
         final int callingUid = callerIsScorer ? Binder.getCallingUid() : Binder.getCallingUid() + 1;
         NetworkScorerAppData appData = new NetworkScorerAppData(callingUid,
                 RECOMMENDATION_SERVICE_COMP, RECOMMENDATION_SERVICE_LABEL,
-                USE_WIFI_ENABLE_ACTIVITY_COMP);
+                USE_WIFI_ENABLE_ACTIVITY_COMP, NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID);
         bindToScorer(appData);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
index e8663a2..9197ccf9 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
@@ -62,6 +62,8 @@
 public class NetworkScorerAppManagerTest {
     private static String MOCK_SERVICE_LABEL = "Mock Service";
     private static String MOCK_OVERRIDEN_SERVICE_LABEL = "Mock Service Label Override";
+    private static String MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID =
+            "Mock Network Available Notification Channel Id";
 
     @Mock private Context mMockContext;
     @Mock private PackageManager mMockPm;
@@ -168,13 +170,30 @@
         mockScoreNetworksGranted(recoComponent.getPackageName());
         mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
                 enableUseOpenWifiComponent.getPackageName());
-        mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
 
         final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
         assertNotNull(activeScorer);
         assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
         assertEquals(924, activeScorer.packageUid);
         assertEquals(enableUseOpenWifiComponent, activeScorer.getEnableUseOpenWifiActivity());
+        assertNull(activeScorer.getNetworkAvailableNotificationChannelId());
+    }
+
+    @Test
+    public void testGetActiveScorer_providerAvailable_networkAvailableNotificationChannelIdSet() {
+        final ComponentName recoComponent = new ComponentName("package1", "class1");
+        setNetworkRecoPackageSetting(recoComponent.getPackageName());
+        mockScoreNetworksGranted(recoComponent.getPackageName());
+        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+                null /* enableUseOpenWifiActivityPackage */, false /* serviceLabelOverride */,
+                true /* setNotificationChannelId */);
+
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNotNull(activeScorer);
+        assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
+        assertEquals(924, activeScorer.packageUid);
+        assertEquals(MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID,
+                activeScorer.getNetworkAvailableNotificationChannelId());
     }
 
     @Test
@@ -368,6 +387,13 @@
 
     private void mockRecommendationServiceAvailable(final ComponentName compName, int packageUid,
             String enableUseOpenWifiActivityPackage, boolean serviceLabelOverride) {
+        mockRecommendationServiceAvailable(compName, packageUid, enableUseOpenWifiActivityPackage,
+                serviceLabelOverride, false);
+    }
+
+    private void mockRecommendationServiceAvailable(final ComponentName compName, int packageUid,
+            String enableUseOpenWifiActivityPackage, boolean serviceLabelOverride,
+            boolean setNotificationChannel) {
         final ResolveInfo serviceInfo = new ResolveInfo();
         serviceInfo.serviceInfo = new ServiceInfo();
         serviceInfo.serviceInfo.name = compName.getClassName();
@@ -390,6 +416,14 @@
         } else {
             serviceInfo.serviceInfo.nonLocalizedLabel = MOCK_SERVICE_LABEL;
         }
+        if (setNotificationChannel) {
+            if (serviceInfo.serviceInfo.metaData == null) {
+                serviceInfo.serviceInfo.metaData = new Bundle();
+            }
+            serviceInfo.serviceInfo.metaData.putString(
+                    NetworkScoreManager.NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID_META_DATA,
+                    MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID);
+        }
 
         final int flags = PackageManager.GET_META_DATA;
         when(mMockPm.resolveService(
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 b12da34..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;
@@ -133,6 +137,8 @@
         mInjector = new TestInjector();
         mAms = new ActivityManagerService(mInjector);
         mAms.mWaitForNetworkTimeoutMs = 100;
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
     }
 
     @After
@@ -601,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) {
@@ -770,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/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 80b2e7d..a7d594c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -422,7 +422,7 @@
         final TestWindowContainer child1 = root.addChildWindow(builder);
         child1.setFillsParent(true);
 
-        assertTrue(root.getOrientation() == expectedOrientation);
+        assertEquals(expectedOrientation, root.getOrientation());
     }
 
     @Test
@@ -805,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/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 48799d2..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);