Merge "Fix warnings in Bridge"
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..9a7d0a6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3673,7 +3673,7 @@
     method public void onLowMemory();
     method public boolean onMenuItemSelected(int, android.view.MenuItem);
     method public boolean onMenuOpened(int, android.view.Menu);
-    method public void onMovedToDisplay(int);
+    method public void onMovedToDisplay(int, android.content.res.Configuration);
     method public void onMultiWindowModeChanged(boolean);
     method public boolean onNavigateUp();
     method public boolean onNavigateUpFromChild(android.app.Activity);
@@ -12695,7 +12695,7 @@
     enum_constant public static final android.graphics.Canvas.VertexMode TRIANGLE_STRIP;
   }
 
-  public class Color {
+  public final class Color {
     ctor public Color();
     method public static int HSVToColor(float[]);
     method public static int HSVToColor(int, float[]);
@@ -12720,6 +12720,7 @@
     method public float getComponent(int);
     method public int getComponentCount();
     method public float[] getComponents();
+    method public float[] getComponents(float[]);
     method public android.graphics.ColorSpace.Model getModel();
     method public float green();
     method public static float green(long);
@@ -21818,23 +21819,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 +21848,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 +22291,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 +23602,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 +24338,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 {
@@ -24548,6 +24560,7 @@
     field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
     field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
     field public static final java.lang.String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
+    field public static final java.lang.String COLUMN_TRANSIENT = "transient";
     field public static final java.lang.String COLUMN_TYPE = "type";
     field public static final java.lang.String INTERACTION_TYPE_FANS = "INTERACTION_TYPE_FANS";
     field public static final java.lang.String INTERACTION_TYPE_FOLLOWERS = "INTERACTION_TYPE_FOLLOWERS";
@@ -24625,6 +24638,7 @@
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
     field public static final java.lang.String COLUMN_SERVICE_ID = "service_id";
     field public static final java.lang.String COLUMN_SERVICE_TYPE = "service_type";
+    field public static final java.lang.String COLUMN_TRANSIENT = "transient";
     field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
     field public static final java.lang.String COLUMN_TYPE = "type";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
@@ -31884,6 +31898,7 @@
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+    method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
     method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
     method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
@@ -34858,7 +34873,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 +37857,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 {
@@ -39363,6 +39379,7 @@
     field public static final java.lang.String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
     field public static final java.lang.String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
     field public static final java.lang.String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
+    field public static final java.lang.String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
     field public static final java.lang.String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
     field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
     field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
@@ -41062,9 +41079,9 @@
   public static final class FontConfig.Font implements android.os.Parcelable {
     method public int describeContents();
     method public android.text.FontConfig.Axis[] getAxes();
-    method public android.os.ParcelFileDescriptor getFd();
     method public java.lang.String getFontName();
     method public int getTtcIndex();
+    method public android.net.Uri getUri();
     method public int getWeight();
     method public boolean isItalic();
     method public void writeToParcel(android.os.Parcel, int);
@@ -45476,7 +45493,7 @@
     method public boolean onKeyUp(int, android.view.KeyEvent);
     method protected void onLayout(boolean, int, int, int, int);
     method protected void onMeasure(int, int);
-    method public void onMovedToDisplay(int);
+    method public void onMovedToDisplay(int, android.content.res.Configuration);
     method protected void onOverScrolled(int, int, boolean, boolean);
     method public void onPointerCaptureChange(boolean);
     method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
diff --git a/api/removed.txt b/api/removed.txt
index 8acf4ad..75da976 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -380,16 +380,6 @@
 
 }
 
-package android.view.textclassifier {
-
-  public abstract interface TextClassifier {
-    method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
-    method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
-    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
-}
-
 package android.webkit {
 
   public class WebViewClient {
diff --git a/api/system-current.txt b/api/system-current.txt
index 48b878e..0b8017d 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";
@@ -3798,7 +3799,7 @@
     method public void onLowMemory();
     method public boolean onMenuItemSelected(int, android.view.MenuItem);
     method public boolean onMenuOpened(int, android.view.Menu);
-    method public void onMovedToDisplay(int);
+    method public void onMovedToDisplay(int, android.content.res.Configuration);
     method public void onMultiWindowModeChanged(boolean);
     method public boolean onNavigateUp();
     method public boolean onNavigateUpFromChild(android.app.Activity);
@@ -11171,6 +11172,7 @@
     method public abstract byte[] getInstantAppCookie();
     method public abstract int getInstantAppCookieMaxSize();
     method public abstract android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
+    method public abstract android.content.ComponentName getInstantAppResolverSettingsComponent();
     method public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
     method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
@@ -11420,6 +11422,7 @@
     field public static final int MATCH_DIRECT_BOOT_UNAWARE = 262144; // 0x40000
     field public static final int MATCH_DISABLED_COMPONENTS = 512; // 0x200
     field public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000
+    field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_INSTANT = 8388608; // 0x800000
     field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
     field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
@@ -13420,7 +13423,7 @@
     enum_constant public static final android.graphics.Canvas.VertexMode TRIANGLE_STRIP;
   }
 
-  public class Color {
+  public final class Color {
     ctor public Color();
     method public static int HSVToColor(float[]);
     method public static int HSVToColor(int, float[]);
@@ -13445,6 +13448,7 @@
     method public float getComponent(int);
     method public int getComponentCount();
     method public float[] getComponents();
+    method public float[] getComponents(float[]);
     method public android.graphics.ColorSpace.Model getModel();
     method public float green();
     method public static float green(long);
@@ -23604,23 +23608,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 +23637,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 +24080,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 +25402,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 +26210,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 {
@@ -34716,6 +34731,7 @@
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+    method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
     method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
     method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
@@ -37748,7 +37764,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 +37813,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 +37885,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 +40990,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 {
@@ -42717,6 +42732,7 @@
     field public static final java.lang.String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
     field public static final java.lang.String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
     field public static final java.lang.String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
+    field public static final java.lang.String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
     field public static final java.lang.String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
     field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
     field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
@@ -44203,6 +44219,7 @@
     method public byte[] getInstantAppCookie();
     method public int getInstantAppCookieMaxSize();
     method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
+    method public android.content.ComponentName getInstantAppResolverSettingsComponent();
     method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
     method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
@@ -44516,9 +44533,9 @@
   public static final class FontConfig.Font implements android.os.Parcelable {
     method public int describeContents();
     method public android.text.FontConfig.Axis[] getAxes();
-    method public android.os.ParcelFileDescriptor getFd();
     method public java.lang.String getFontName();
     method public int getTtcIndex();
+    method public android.net.Uri getUri();
     method public int getWeight();
     method public boolean isItalic();
     method public void writeToParcel(android.os.Parcel, int);
@@ -48931,7 +48948,7 @@
     method public boolean onKeyUp(int, android.view.KeyEvent);
     method protected void onLayout(boolean, int, int, int, int);
     method protected void onMeasure(int, int);
-    method public void onMovedToDisplay(int);
+    method public void onMovedToDisplay(int, android.content.res.Configuration);
     method protected void onOverScrolled(int, int, boolean, boolean);
     method public void onPointerCaptureChange(boolean);
     method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index a2fcbcd..3aa9398 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -374,16 +374,6 @@
 
 }
 
-package android.view.textclassifier {
-
-  public abstract interface TextClassifier {
-    method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
-    method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
-    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
-}
-
 package android.webkit {
 
   public class WebViewClient {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4d8d7f2..10cf5ce 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3675,7 +3675,7 @@
     method public void onLowMemory();
     method public boolean onMenuItemSelected(int, android.view.MenuItem);
     method public boolean onMenuOpened(int, android.view.Menu);
-    method public void onMovedToDisplay(int);
+    method public void onMovedToDisplay(int, android.content.res.Configuration);
     method public void onMultiWindowModeChanged(boolean);
     method public boolean onNavigateUp();
     method public boolean onNavigateUpFromChild(android.app.Activity);
@@ -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);
@@ -12743,7 +12745,7 @@
     enum_constant public static final android.graphics.Canvas.VertexMode TRIANGLE_STRIP;
   }
 
-  public class Color {
+  public final class Color {
     ctor public Color();
     method public static int HSVToColor(float[]);
     method public static int HSVToColor(int, float[]);
@@ -12768,6 +12770,7 @@
     method public float getComponent(int);
     method public int getComponentCount();
     method public float[] getComponents();
+    method public float[] getComponents(float[]);
     method public android.graphics.ColorSpace.Model getModel();
     method public float green();
     method public static float green(long);
@@ -21929,23 +21932,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 +21961,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 +22404,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 +23715,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 +24451,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 {
@@ -24659,6 +24673,7 @@
     field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
     field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
     field public static final java.lang.String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
+    field public static final java.lang.String COLUMN_TRANSIENT = "transient";
     field public static final java.lang.String COLUMN_TYPE = "type";
     field public static final java.lang.String INTERACTION_TYPE_FANS = "INTERACTION_TYPE_FANS";
     field public static final java.lang.String INTERACTION_TYPE_FOLLOWERS = "INTERACTION_TYPE_FOLLOWERS";
@@ -24736,6 +24751,7 @@
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
     field public static final java.lang.String COLUMN_SERVICE_ID = "service_id";
     field public static final java.lang.String COLUMN_SERVICE_TYPE = "service_type";
+    field public static final java.lang.String COLUMN_TRANSIENT = "transient";
     field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
     field public static final java.lang.String COLUMN_TYPE = "type";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
@@ -32019,6 +32035,7 @@
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+    method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
     method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
     method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
@@ -34997,7 +35014,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 +38060,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 {
@@ -39564,6 +39582,7 @@
     field public static final java.lang.String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
     field public static final java.lang.String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
     field public static final java.lang.String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
+    field public static final java.lang.String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
     field public static final java.lang.String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
     field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
     field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
@@ -41267,9 +41286,9 @@
   public static final class FontConfig.Font implements android.os.Parcelable {
     method public int describeContents();
     method public android.text.FontConfig.Axis[] getAxes();
-    method public android.os.ParcelFileDescriptor getFd();
     method public java.lang.String getFontName();
     method public int getTtcIndex();
+    method public android.net.Uri getUri();
     method public int getWeight();
     method public boolean isItalic();
     method public void writeToParcel(android.os.Parcel, int);
@@ -45849,7 +45868,7 @@
     method public boolean onKeyUp(int, android.view.KeyEvent);
     method protected void onLayout(boolean, int, int, int, int);
     method protected void onMeasure(int, int);
-    method public void onMovedToDisplay(int);
+    method public void onMovedToDisplay(int, android.content.res.Configuration);
     method protected void onOverScrolled(int, int, boolean, boolean);
     method public void onPointerCaptureChange(boolean);
     method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 8acf4ad..75da976 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -380,16 +380,6 @@
 
 }
 
-package android.view.textclassifier {
-
-  public abstract interface TextClassifier {
-    method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
-    method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
-    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
-}
-
 package android.webkit {
 
   public class WebViewClient {
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index db3772d..658d662 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -94,6 +94,8 @@
             runGetFbeMode();
         } else if ("fstrim".equals(op)) {
             runFstrim();
+        } else if ("set-virtual-disk".equals(op)) {
+            runSetVirtualDisk();
         } else {
             throw new IllegalArgumentException();
         }
@@ -225,6 +227,12 @@
         mSm.fstrim(0);
     }
 
+    public void runSetVirtualDisk() throws RemoteException {
+        final boolean virtualDisk = Boolean.parseBoolean(nextArg());
+        mSm.setDebugFlags(virtualDisk ? StorageManager.DEBUG_VIRTUAL_DISK : 0,
+                StorageManager.DEBUG_VIRTUAL_DISK);
+    }
+
     private String nextArg() {
         if (mNextArg >= mArgs.length) {
             return null;
@@ -240,6 +248,7 @@
         System.err.println("       sm has-adoptable");
         System.err.println("       sm get-primary-storage-uuid");
         System.err.println("       sm set-force-adoptable [true|false]");
+        System.err.println("       sm set-virtual-disk [true|false]");
         System.err.println("");
         System.err.println("       sm partition DISK [public|private|mixed] [ratio]");
         System.err.println("       sm mount VOLUME");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index fa1de03..147b5d0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1990,27 +1990,32 @@
         }
     }
 
-    void dispatchMovedToDisplay(int displayId) {
+    void dispatchMovedToDisplay(int displayId, Configuration config) {
         updateDisplay(displayId);
-        onMovedToDisplay(displayId);
+        onMovedToDisplay(displayId, config);
     }
 
     /**
      * Called by the system when the activity is moved from one display to another without
      * recreation. This means that this activity is declared to handle all changes to configuration
      * that happened when it was switched to another display, so it wasn't destroyed and created
-     * again. This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
-     * applied configuration actually changed.
+     * again.
+     *
+     * <p>This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+     * applied configuration actually changed. It is up to app developer to choose whether to handle
+     * the change in this method or in the following {@link #onConfigurationChanged(Configuration)}
+     * call.
      *
      * <p>Use this callback to track changes to the displays if some activity functionality relies
      * on an association with some display properties.
      *
      * @param displayId The id of the display to which activity was moved.
+     * @param config Configuration of the activity resources on new display after move.
      *
      * @see #onConfigurationChanged(Configuration)
-     * @see View#onMovedToDisplay(int)
+     * @see View#onMovedToDisplay(int, Configuration)
      */
-    public void onMovedToDisplay(int displayId) {
+    public void onMovedToDisplay(int displayId, Configuration config) {
     }
 
     /**
@@ -2735,6 +2740,10 @@
                 return true;
             }
             return false;
+        } else if (keyCode == KeyEvent.KEYCODE_TAB) {
+            // Don't consume TAB here since it's used for navigation. Arrow keys
+            // aren't considered "typing keys" so they already won't get consumed.
+            return false;
         } else {
             // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
             boolean clearSpannable = false;
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/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b4e6bd5..b5d1fa8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -808,11 +808,6 @@
         public final void scheduleReceiver(Intent intent, ActivityInfo info,
                 CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
                 boolean sync, int sendingUser, int processState) {
-            // TODO: Debugging added for bug:36406078 . Remove when done
-            if (Log.isLoggable("36406078", Log.DEBUG)) {
-                Log.d(TAG, "scheduleReceiver");
-            }
-
             updateProcessState(processState, false);
             ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                     sync, false, mAppThread.asBinder(), sendingUser);
@@ -899,11 +894,6 @@
                 CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                 String buildSerial) {
 
-            // TODO: Debugging added for bug:36406078 . Remove when done
-            if (Log.isLoggable("36406078", Log.DEBUG)) {
-                Log.d(TAG, "bindApplication: " + processName);
-            }
-
             if (services != null) {
                 // Setup the service cache in the ServiceManager
                 ServiceManager.initServiceCache(services);
@@ -3239,10 +3229,6 @@
         if (receiver.getPendingResult() != null) {
             data.finish();
         }
-        // TODO: Debugging added for bug:36406078 . Remove when done
-        if (Log.isLoggable("36406078", Log.DEBUG)) {
-            Log.d(TAG, "handleReceiver done");
-        }
     }
 
     // Instantiate a BackupAgent and tell it that it's alive
@@ -4815,16 +4801,18 @@
      *                      {@link ActivityClientRecord#overrideConfig}.
      * @param displayId The id of the display where the Activity currently resides.
      * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+     * @return {@link Configuration} instance sent to client, null if not sent.
      */
-    private void performConfigurationChangedForActivity(ActivityClientRecord r,
+    private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
             Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
         r.tmpConfig.setTo(newBaseConfig);
         if (r.overrideConfig != null) {
             r.tmpConfig.updateFrom(r.overrideConfig);
         }
-        performActivityConfigurationChanged(r.activity, r.tmpConfig, r.overrideConfig, displayId,
-                movedToDifferentDisplay);
+        final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
+                r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);
         freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
+        return reportedConfig;
     }
 
     /**
@@ -4878,9 +4866,11 @@
      *                         ActivityManager.
      * @param displayId Id of the display where activity currently resides.
      * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+     * @return Configuration sent to client, null if no changes and not moved to different display.
      */
-    private void performActivityConfigurationChanged(Activity activity, Configuration newConfig,
-            Configuration amOverrideConfig, int displayId, boolean movedToDifferentDisplay) {
+    private Configuration performActivityConfigurationChanged(Activity activity,
+            Configuration newConfig, Configuration amOverrideConfig, int displayId,
+            boolean movedToDifferentDisplay) {
         if (activity == null) {
             throw new IllegalArgumentException("No activity provided.");
         }
@@ -4911,7 +4901,7 @@
         }
         if (!shouldChangeConfig && !movedToDifferentDisplay) {
             // Nothing significant, don't proceed with updating and reporting.
-            return;
+            return null;
         }
 
         // Propagate the configuration change to ResourcesManager and Activity.
@@ -4934,22 +4924,22 @@
         activity.mConfigChangeFlags = 0;
         activity.mCurrentConfig = new Configuration(newConfig);
 
+        // Apply the ContextThemeWrapper override if necessary.
+        // NOTE: Make sure the configurations are not modified, as they are treated as immutable
+        // in many places.
+        final Configuration configToReport = createNewConfigAndUpdateIfNotNull(newConfig,
+                contextThemeWrapperOverrideConfig);
+
         if (!REPORT_TO_ACTIVITY) {
             // Not configured to report to activity.
-            return;
+            return configToReport;
         }
 
         if (movedToDifferentDisplay) {
-            activity.dispatchMovedToDisplay(displayId);
+            activity.dispatchMovedToDisplay(displayId, configToReport);
         }
 
         if (shouldChangeConfig) {
-            // Apply the ContextThemeWrapper override if necessary.
-            // NOTE: Make sure the configurations are not modified, as they are treated as immutable
-            // in many places.
-            final Configuration configToReport = createNewConfigAndUpdateIfNotNull(
-                    newConfig, contextThemeWrapperOverrideConfig);
-
             activity.mCalled = false;
             activity.onConfigurationChanged(configToReport);
             if (!activity.mCalled) {
@@ -4957,6 +4947,8 @@
                                 " did not call through to super.onConfigurationChanged()");
             }
         }
+
+        return configToReport;
     }
 
     public final void applyConfigurationToResources(Configuration config) {
@@ -5129,10 +5121,10 @@
                     + r.activityInfo.name + ", displayId=" + displayId
                     + ", config=" + data.overrideConfig);
 
-            performConfigurationChangedForActivity(r, mCompatConfiguration, displayId,
-                    true /* movedToDifferentDisplay */);
+            final Configuration reportedConfig = performConfigurationChangedForActivity(r,
+                    mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
             if (viewRoot != null) {
-                viewRoot.onMovedToDisplay(displayId);
+                viewRoot.onMovedToDisplay(displayId, reportedConfig);
             }
         } else {
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
@@ -5142,7 +5134,7 @@
         // Notify the ViewRootImpl instance about configuration changes. It may have initiated this
         // update to make sure that resources are updated before updating itself.
         if (viewRoot != null) {
-            viewRoot.updateConfiguration();
+            viewRoot.updateConfiguration(displayId);
         }
         mSomeActivitiesChanged = true;
     }
@@ -5778,10 +5770,6 @@
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        // TODO: Debugging added for bug:36406078 . Remove when done
-        if (Log.isLoggable("36406078", Log.DEBUG)) {
-            Log.d(TAG, "handleBindApplication done");
-        }
     }
 
     /*package*/ final void finishInstrumentation(int resultCode, Bundle results) {
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index ef2db4a..2062930 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -18,6 +18,7 @@
 
 import android.os.Build;
 import android.os.Trace;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import com.android.internal.os.PathClassLoaderFactory;
 import dalvik.system.PathClassLoader;
@@ -29,8 +30,16 @@
     }
 
     ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
-                                      String librarySearchPath, String libraryPermittedPath,
-                                      ClassLoader parent) {
+                               String librarySearchPath, String libraryPermittedPath,
+                               ClassLoader parent) {
+        // For normal usage the cache key used is the same as the zip path.
+        return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
+                              libraryPermittedPath, parent, zip);
+    }
+
+    private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
+                                       String librarySearchPath, String libraryPermittedPath,
+                                       ClassLoader parent, String cacheKey) {
         /*
          * This is the parent we use if they pass "null" in.  In theory
          * this should be the "system" class loader; in practice we
@@ -50,7 +59,7 @@
              * new ClassLoader for the zip archive.
              */
             if (parent == baseParent) {
-                ClassLoader loader = mLoaders.get(zip);
+                ClassLoader loader = mLoaders.get(cacheKey);
                 if (loader != null) {
                     return loader;
                 }
@@ -71,7 +80,7 @@
                 setupVulkanLayerPath(pathClassloader, librarySearchPath);
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
-                mLoaders.put(zip, pathClassloader);
+                mLoaders.put(cacheKey, pathClassloader);
                 return pathClassloader;
             }
 
@@ -87,12 +96,16 @@
      * by this class. This is used in the WebView zygote, where its presence in the cache speeds up
      * startup and enables memory sharing.
      */
-    public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath) {
-      // The correct paths are calculated by WebViewZygote in the system server and passed to
-      // us here. We hardcode the other parameters: WebView always targets the current SDK,
-      // does not need to use non-public system libraries, and uses the base classloader as its
-      // parent to permit usage of the cache.
-      return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null);
+    public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath,
+                                                        String cacheKey) {
+        // The correct paths are calculated by WebViewZygote in the system server and passed to
+        // us here. We hardcode the other parameters: WebView always targets the current SDK,
+        // does not need to use non-public system libraries, and uses the base classloader as its
+        // parent to permit usage of the cache.
+        // The cache key is passed separately to enable the stub WebView to be cached under the
+        // stub's APK path, when the actual package path is the donor APK.
+        return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null,
+                              cacheKey);
     }
 
     private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 286f8570..461f9cc 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2630,4 +2630,13 @@
             throw e.rethrowAsRuntimeException();
         }
     }
+
+    @Override
+    public ComponentName getInstantAppResolverSettingsComponent() {
+        try {
+            return mPM.getInstantAppResolverSettingsComponent();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 82229d5..b9d1d91 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -629,6 +629,11 @@
      */
     void setDisablePreviewScreenshots(IBinder token, boolean disable);
 
+    /**
+     * Return the user id of last resumed activity.
+     */
+    int getLastResumedActivityUserId();
+
     // WARNING: when these transactions are updated, check if they are any callers on the native
     // side. If so, make sure they are using the correct transaction ids and arguments.
     // If a transaction which will also be used on the native side is being inserted, add it
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 124749a..f719749 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -93,6 +93,7 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Build;
+import android.os.Debug;
 import android.os.DropBoxManager;
 import android.os.HardwarePropertiesManager;
 import android.os.IBatteryPropertiesRegistrar;
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 9e2eb84..5d1e8ec 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
@@ -778,7 +778,7 @@
 
     /**
      * Set the preferred connection PHY for this app. Please note that this is just a
-     * recommendation, wether the PHY change will happen depends on other applications peferences,
+     * recommendation, whether the PHY change will happen depends on other applications peferences,
      * local and remote controller capabilities. Controller can override these settings.
      * <p>
      * {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index c991e2f..2df2ed8 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
@@ -550,7 +550,7 @@
 
     /**
      * Set the preferred connection PHY for this app. Please note that this is just a
-     * recommendation, wether the PHY change will happen depends on other applications peferences,
+     * recommendation, whether the PHY change will happen depends on other applications peferences,
      * local and remote controller capabilities. Controller can override these settings.
      * <p>
      * {@link BluetoothGattServerCallback#onPhyUpdate} will be triggered as a result of this call, even
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 652a1c60..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/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index fe1f425..d36c0d6 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -279,7 +279,7 @@
          * When set to true, advertising set will advertise 4.x Spec compliant
          * advertisements.
          *
-         * @param isLegacy wether legacy advertising mode should be used.
+         * @param isLegacy whether legacy advertising mode should be used.
          */
         public Builder setLegacyMode(boolean isLegacy) {
             this.isLegacy = isLegacy;
@@ -287,12 +287,12 @@
         }
 
         /**
-         * Set wether advertiser address should be ommited from all packets. If this
+         * Set whether advertiser address should be ommited from all packets. If this
          * mode is used, periodic advertising can't be enabled for this set.
          *
          * This is used only if legacy mode is not used.
          *
-         * @param isAnonymous wether anonymous advertising should be used.
+         * @param isAnonymous whether anonymous advertising should be used.
          */
         public Builder setAnonymous(boolean isAnonymous) {
             this.isAnonymous = isAnonymous;
@@ -300,11 +300,11 @@
         }
 
         /**
-         * Set wether TX power should be included in the extended header.
+         * Set whether TX power should be included in the extended header.
          *
          * This is used only if legacy mode is not used.
          *
-         * @param includeTxPower wether TX power should be included in extended
+         * @param includeTxPower whether TX power should be included in extended
          * header
          */
         public Builder setIncludeTxPower(boolean includeTxPower) {
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
index ebc92bd..149540c 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
@@ -93,7 +93,7 @@
         private int interval = INTERVAL_MAX;
 
         /**
-         * Set wether the Periodic Advertising should be enabled for this set.
+         * Set whether the Periodic Advertising should be enabled for this set.
          */
         public Builder setEnable(boolean enable) {
             this.enable = enable;
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index bb844a3..919f4ba 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -27,6 +27,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A request for the user to select a companion device to associate with.
@@ -69,6 +70,20 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AssociationRequest that = (AssociationRequest) o;
+        return mSingleDevice == that.mSingleDevice &&
+                Objects.equals(mDeviceFilters, that.mDeviceFilters);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSingleDevice, mDeviceFilters);
+    }
+
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeByte((byte) (mSingleDevice ? 1 : 0));
         dest.writeParcelableList(mDeviceFilters, flags);
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 1d8df7f..84e1536 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -35,6 +35,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -123,6 +124,22 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BluetoothDeviceFilter that = (BluetoothDeviceFilter) o;
+        return Objects.equals(mNamePattern, that.mNamePattern) &&
+                Objects.equals(mAddress, that.mAddress) &&
+                Objects.equals(mServiceUuids, that.mServiceUuids) &&
+                Objects.equals(mServiceUuidMasks, that.mServiceUuidMasks);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNamePattern, mAddress, mServiceUuids, mServiceUuidMasks);
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java
index 8a316f1..3665d1b 100644
--- a/core/java/android/companion/BluetoothDeviceFilterUtils.java
+++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java
@@ -37,7 +37,7 @@
     private BluetoothDeviceFilterUtils() {}
 
     private static final boolean DEBUG = false;
-    private static final String LOG_TAG = "BluetoothDeviceFilterUtil";
+    private static final String LOG_TAG = "BluetoothDeviceFilterUtils";
 
     @Nullable
     static String patternToString(@Nullable Pattern p) {
@@ -50,8 +50,10 @@
     }
 
     static boolean matches(ScanFilter filter, BluetoothDevice device) {
-        return matchesAddress(filter.getDeviceAddress(), device)
+        boolean result = matchesAddress(filter.getDeviceAddress(), device)
                 && matchesServiceUuid(filter.getServiceUuid(), filter.getServiceUuidMask(), device);
+        if (DEBUG) debugLogMatchResult(result, device, filter);
+        return result;
     }
 
     static boolean matchesAddress(String deviceAddress, BluetoothDevice device) {
diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.java b/core/java/android/companion/BluetoothLEDeviceFilter.java
index e057fbc..5a1b29d 100644
--- a/core/java/android/companion/BluetoothLEDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLEDeviceFilter.java
@@ -31,11 +31,14 @@
 import android.os.Parcel;
 import android.provider.OneTimeUseBuilder;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.ObjectUtils;
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -45,6 +48,9 @@
  */
 public final class BluetoothLEDeviceFilter implements DeviceFilter<ScanResult> {
 
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "BluetoothLEDeviceFilter";
+
     private static final int RENAME_PREFIX_LENGTH_LIMIT = 10;
 
     private final Pattern mNamePattern;
@@ -143,9 +149,13 @@
     /** @hide */
     @Override
     public boolean matches(ScanResult device) {
-        return matches(device.getDevice())
-                && BitUtils.maskedEquals(device.getScanRecord().getBytes(),
-                        mRawDataFilter, mRawDataFilterMask);
+        boolean result = matches(device.getDevice())
+                && (mRawDataFilter == null
+                    || BitUtils.maskedEquals(device.getScanRecord().getBytes(),
+                            mRawDataFilter, mRawDataFilterMask));
+        if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device +
+                ") -> " + result);
+        return result;
     }
 
     private boolean matches(BluetoothDevice device) {
@@ -160,9 +170,39 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BluetoothLEDeviceFilter that = (BluetoothLEDeviceFilter) o;
+        return mRenameBytesFrom == that.mRenameBytesFrom &&
+                mRenameBytesTo == that.mRenameBytesTo &&
+                mRenameBytesReverseOrder == that.mRenameBytesReverseOrder &&
+                Objects.equals(mNamePattern, that.mNamePattern) &&
+                Objects.equals(mScanFilter, that.mScanFilter) &&
+                Arrays.equals(mRawDataFilter, that.mRawDataFilter) &&
+                Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) &&
+                Objects.equals(mRenamePrefix, that.mRenamePrefix) &&
+                Objects.equals(mRenameSuffix, that.mRenameSuffix);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask,
+                mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesTo,
+                mRenameBytesReverseOrder);
+    }
+
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(patternToString(getNamePattern()));
         dest.writeParcelable(mScanFilter, flags);
+        dest.writeByteArray(mRawDataFilter);
+        dest.writeByteArray(mRawDataFilterMask);
+        dest.writeString(mRenamePrefix);
+        dest.writeString(mRenameSuffix);
+        dest.writeInt(mRenameBytesFrom);
+        dest.writeInt(mRenameBytesTo);
+        dest.writeBoolean(mRenameBytesReverseOrder);
     }
 
     @Override
@@ -174,13 +214,23 @@
             = new Creator<BluetoothLEDeviceFilter>() {
         @Override
         public BluetoothLEDeviceFilter createFromParcel(Parcel in) {
-            return new BluetoothLEDeviceFilter.Builder()
+            Builder builder = new Builder()
                     .setNamePattern(patternFromString(in.readString()))
-                    .setScanFilter(in.readParcelable(null))
-                    .setRawDataFilter(in.readBlob(), in.readBlob())
-                    .setRename(in.readString(), in.readString(),
-                            in.readInt(), in.readInt(), in.readBoolean())
-                    .build();
+                    .setScanFilter(in.readParcelable(null));
+            byte[] rawDataFilter = in.createByteArray();
+            byte[] rawDataFilterMask = in.createByteArray();
+            if (rawDataFilter != null) {
+                builder.setRawDataFilter(rawDataFilter, rawDataFilterMask);
+            }
+            String renamePrefix = in.readString();
+            String suffix = in.readString();
+            int bytesFrom = in.readInt();
+            int bytesTo = in.readInt();
+            boolean bytesReverseOrder = in.readBoolean();
+            if (renamePrefix != null) {
+                builder.setRename(renamePrefix, suffix, bytesFrom, bytesTo, bytesReverseOrder);
+            }
+            return builder.build();
         }
 
         @Override
@@ -240,12 +290,14 @@
          */
         @NonNull
         public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
-                @NonNull byte[] rawDataFilterMask) {
+                @Nullable byte[] rawDataFilterMask) {
             checkNotUsed();
-            checkArgument(rawDataFilter.length == rawDataFilterMask.length,
+            Preconditions.checkNotNull(rawDataFilter);
+            checkArgument(rawDataFilterMask == null ||
+                    rawDataFilter.length == rawDataFilterMask.length,
                     "Mask and filter should be the same length");
-            mRawDataFilter = Preconditions.checkNotNull(rawDataFilter);
-            mRawDataFilterMask = Preconditions.checkNotNull(rawDataFilterMask);
+            mRawDataFilter = rawDataFilter;
+            mRawDataFilterMask = rawDataFilterMask;
             return this;
         }
 
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 78e3de4..7b38863 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -17,6 +17,8 @@
 package android.companion;
 
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -24,7 +26,6 @@
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -43,7 +44,7 @@
  */
 public final class CompanionDeviceManager {
 
-    private static final boolean DEBUG = false; //TODO
+    private static final boolean DEBUG = false;
     private static final String LOG_TAG = "CompanionDeviceManager";
 
     /**
@@ -129,10 +130,9 @@
         if (!checkFeaturePresent()) {
             return;
         }
-
-        final Handler finalHandler = handler != null
-                ? handler
-                : new Handler(Looper.getMainLooper());
+        checkNotNull(request, "Request cannot be null");
+        checkNotNull(callback, "Callback cannot be null");
+        final Handler finalHandler = Handler.mainIfNull(handler);
         try {
             mService.associate(
                     request,
diff --git a/core/java/android/companion/WifiDeviceFilter.java b/core/java/android/companion/WifiDeviceFilter.java
index 1ab9ce1..b6e704c 100644
--- a/core/java/android/companion/WifiDeviceFilter.java
+++ b/core/java/android/companion/WifiDeviceFilter.java
@@ -29,6 +29,7 @@
 import android.os.Parcel;
 import android.provider.OneTimeUseBuilder;
 
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -75,6 +76,19 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        WifiDeviceFilter that = (WifiDeviceFilter) o;
+        return Objects.equals(mNamePattern, that.mNamePattern);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNamePattern);
+    }
+
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(patternToString(getNamePattern()));
     }
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index c28172c..f1c2f34 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -150,7 +150,7 @@
 
     public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
         synchronized (mPrimaryClipChangedListeners) {
-            if (mPrimaryClipChangedListeners.size() == 0) {
+            if (mPrimaryClipChangedListeners.isEmpty()) {
                 try {
                     mService.addPrimaryClipChangedListener(
                             mPrimaryClipChangedServiceListener, mContext.getOpPackageName());
@@ -165,7 +165,7 @@
     public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
         synchronized (mPrimaryClipChangedListeners) {
             mPrimaryClipChangedListeners.remove(what);
-            if (mPrimaryClipChangedListeners.size() == 0) {
+            if (mPrimaryClipChangedListeners.isEmpty()) {
                 try {
                     mService.removePrimaryClipChangedListener(
                             mPrimaryClipChangedServiceListener);
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index fd1e24a..8f3a317 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -508,14 +508,14 @@
         /** Create a ContentProviderOperation from this {@link Builder}. */
         public ContentProviderOperation build() {
             if (mType == TYPE_UPDATE) {
-                if ((mValues == null || mValues.size() == 0)
-                        && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)) {
+                if ((mValues == null || mValues.isEmpty())
+                      && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())) {
                     throw new IllegalArgumentException("Empty values");
                 }
             }
             if (mType == TYPE_ASSERT) {
-                if ((mValues == null || mValues.size() == 0)
-                        && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)
+                if ((mValues == null || mValues.isEmpty())
+                      && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())
                         && (mExpectedCount == null)) {
                     throw new IllegalArgumentException("Empty values");
                 }
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 3a87cb3..6f93798 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -204,6 +204,17 @@
     }
 
     /**
+     * Indicates whether this collection is empty.
+     *
+     * @return true iff size == 0
+     * {@hide}
+     * TODO: consider exposing this new method publicly
+     */
+    public boolean isEmpty() {
+        return mValues.isEmpty();
+    }
+
+    /**
      * Remove a single value.
      *
      * @param key the name of the value to remove
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index fb86791..7890a96 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1532,6 +1532,19 @@
             = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
 
     /**
+     * Activity Action: Launch ephemeral settings.
+     *
+     * <p class="note">
+     * This is a protected intent that can only be sent by the system.
+     * </p>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS
+            = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS";
+
+    /**
      * Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
      * package.  Specifies the installer package name; this package will receive the
      * {@link #ACTION_APP_ERROR} intent.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 59b022d..147df76 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -630,4 +630,6 @@
     boolean canRequestPackageInstalls(String packageName, int userId);
 
     void deletePreloadsFileCache();
+
+    ComponentName getInstantAppResolverSettingsComponent();
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 71db5d3..136c13b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -153,6 +153,7 @@
             MATCH_UNINSTALLED_PACKAGES,
             MATCH_SYSTEM_ONLY,
             MATCH_DEBUG_TRIAGED_MISSING,
+            MATCH_DISABLED_COMPONENTS,
             MATCH_DISABLED_UNTIL_USED_COMPONENTS,
             MATCH_INSTANT,
             GET_DISABLED_UNTIL_USED_COMPONENTS,
@@ -431,6 +432,7 @@
      * This will not return information on any unbundled update to system components.
      * @hide
      */
+    @SystemApi
     public static final int MATCH_FACTORY_ONLY = 0x00200000;
 
     /**
@@ -3730,6 +3732,7 @@
      *
      * @param flags Additional option flags. Use any combination of
      * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+     * {@link #MATCH_DISABLED_COMPONENTS}, {@link #MATCH_DISABLED_UNTIL_USED_COMPONENTS}
      * {@link #MATCH_SYSTEM_ONLY}, {@link #MATCH_UNINSTALLED_PACKAGES}
      * to modify the data returned.
      *
@@ -3743,6 +3746,7 @@
      *
      * @see #GET_META_DATA
      * @see #GET_SHARED_LIBRARY_FILES
+     * @see #MATCH_DISABLED_COMPONENTS
      * @see #MATCH_DISABLED_UNTIL_USED_COMPONENTS
      * @see #MATCH_SYSTEM_ONLY
      * @see #MATCH_UNINSTALLED_PACKAGES
@@ -3757,6 +3761,7 @@
      *
      * @param flags Additional option flags. Use any combination of
      * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+     * {@link #MATCH_DISABLED_COMPONENTS}, {@link #MATCH_DISABLED_UNTIL_USED_COMPONENTS}
      * {@link #MATCH_SYSTEM_ONLY}, {@link #MATCH_UNINSTALLED_PACKAGES}
      * to modify the data returned.
      * @param userId The user for whom the installed applications are to be listed
@@ -3772,6 +3777,7 @@
      *
      * @see #GET_META_DATA
      * @see #GET_SHARED_LIBRARY_FILES
+     * @see #MATCH_DISABLED_COMPONENTS
      * @see #MATCH_DISABLED_UNTIL_USED_COMPONENTS
      * @see #MATCH_SYSTEM_ONLY
      * @see #MATCH_UNINSTALLED_PACKAGES
@@ -6233,4 +6239,14 @@
      * @see {@link android.provider.Settings#ACTION_MANAGE_EXTERNAL_SOURCES}
      */
     public abstract boolean canRequestPackageInstalls();
+
+    /**
+     * Return the {@link ComponentName} of the activity providing Settings for the Instant App
+     * resolver.
+     *
+     * @see {@link android.content.intent#ACTION_EPHEMERAL_RESOLVER_SETTINGS}
+     * @hide
+     */
+    @SystemApi
+    public abstract ComponentName getInstantAppResolverSettingsComponent();
 }
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 6272822..370af17 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -172,6 +172,7 @@
      * @param packageName The package name.
      * @param userId The user for which to check.
      * @return Whether was launched.
+     * @throws IllegalArgumentException if the package is not found
      */
     public abstract boolean wasPackageEverLaunched(String packageName, int userId);
 
@@ -241,6 +242,7 @@
     public abstract void grantEphemeralAccess(int userId, Intent intent,
             int targetAppId, int ephemeralAppId);
 
+    public abstract boolean isInstantAppInstallerComponent(ComponentName component);
     /**
      * Prunes instant apps and state associated with uninstalled
      * instant apps according to the current platform policy.
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/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 8e17832..fe849b8 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1449,7 +1449,7 @@
             sql.append('(');
 
             Object[] bindArgs = null;
-            int size = (initialValues != null && initialValues.size() > 0)
+            int size = (initialValues != null && !initialValues.isEmpty())
                     ? initialValues.size() : 0;
             if (size > 0) {
                 bindArgs = new Object[size];
@@ -1541,7 +1541,7 @@
      */
     public int updateWithOnConflict(String table, ContentValues values,
             String whereClause, String[] whereArgs, int conflictAlgorithm) {
-        if (values == null || values.size() == 0) {
+        if (values == null || values.isEmpty()) {
             throw new IllegalArgumentException("Empty values");
         }
 
diff --git a/core/java/android/hardware/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/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index dbe1394..00e047d 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1112,6 +1112,8 @@
                 return ImageFormat.DEPTH_POINT_CLOUD;
             case HAL_PIXEL_FORMAT_Y16:
                 return ImageFormat.DEPTH16;
+            case HAL_PIXEL_FORMAT_RAW16:
+                return ImageFormat.RAW_DEPTH;
             case ImageFormat.JPEG:
                 throw new IllegalArgumentException(
                         "ImageFormat.JPEG is an unknown internal format");
@@ -1179,6 +1181,8 @@
                 return HAL_PIXEL_FORMAT_BLOB;
             case ImageFormat.DEPTH16:
                 return HAL_PIXEL_FORMAT_Y16;
+            case ImageFormat.RAW_DEPTH:
+                return HAL_PIXEL_FORMAT_RAW16;
             default:
                 return format;
         }
@@ -1220,6 +1224,7 @@
                 return HAL_DATASPACE_V0_JFIF;
             case ImageFormat.DEPTH_POINT_CLOUD:
             case ImageFormat.DEPTH16:
+            case ImageFormat.RAW_DEPTH:
                 return HAL_DATASPACE_DEPTH;
             default:
                 return HAL_DATASPACE_UNKNOWN;
@@ -1609,6 +1614,8 @@
                 return "DEPTH16";
             case ImageFormat.DEPTH_POINT_CLOUD:
                 return "DEPTH_POINT_CLOUD";
+            case ImageFormat.RAW_DEPTH:
+                return "RAW_DEPTH";
             case ImageFormat.PRIVATE:
                 return "PRIVATE";
             default:
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 14bb923..58c3d24 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -196,6 +196,19 @@
             return mIsCaptureSupported;
         }
 
+        /**
+         * {@code true} if the module supports background scanning. At the given time it may not
+         * be available though, see {@link RadioTuner#startBackgroundScan()}.
+         *
+         * @return {@code true} if background scanning is supported (not necessary available
+         * at a given time), {@code false} otherwise.
+         *
+         * @hide FutureFeature
+         */
+        public boolean isBackgroundScanningSupported() {
+            return false;
+        }
+
         /** List of descriptors for all bands supported by this module.
          * @return an array of {@link BandDescriptor}.
          */
diff --git a/core/java/android/hardware/radio/RadioModule.java b/core/java/android/hardware/radio/RadioModule.java
index 8964893..033403a 100644
--- a/core/java/android/hardware/radio/RadioModule.java
+++ b/core/java/android/hardware/radio/RadioModule.java
@@ -79,6 +79,8 @@
 
     public native int getProgramInformation(RadioManager.ProgramInfo[] info);
 
+    public native boolean startBackgroundScan();
+
     public native @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable String filter);
 
     public native boolean isAntennaConnected();
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index c8034eb..1159d7d 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -212,6 +212,23 @@
     public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
 
     /**
+     * Initiates a background scan to update internally cached program list.
+     *
+     * It may not be necessary to initiate the scan explicitly - the scan MAY be performed on boot.
+     *
+     * The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
+     * be called if the return value of this call was {@code true}. As result of this call
+     * programListChanged may be triggered (if the scanned list differs).
+     *
+     * @return {@code true} if the scan was properly scheduled, {@code false} if the scan feature
+     * is unavailable; ie. temporarily due to ongoing foreground playback in single-tuner device
+     * or permanently if the feature is not supported
+     * (see ModuleProperties#isBackgroundScanningSupported()).
+     * @hide FutureFeature
+     */
+    public abstract boolean startBackgroundScan();
+
+    /**
      * Get the list of discovered radio stations.
      *
      * To get the full list, set filter to null or empty string. Otherwise, client application
@@ -219,7 +236,8 @@
      *
      * @param filter vendor-specific selector for radio stations.
      * @return a list of radio stations.
-     * @throws IllegalStateException if the scan is in progress or has not been started.
+     * @throws IllegalStateException if the scan is in progress or has not been started,
+     *         startBackgroundScan() call may fix it.
      * @throws IllegalArgumentException if the filter argument is not valid.
      * @hide FutureFeature
      */
@@ -317,6 +335,32 @@
          * with control set to {@code true}.
          */
         public void onControlChanged(boolean control) {}
+
+        /**
+         * onBackgroundScanAvailabilityChange() is called when background scan
+         * feature becomes available or not.
+         *
+         * @param isAvailable true, if the tuner turned temporarily background-
+         *                    capable, false in the other case.
+         * @hide FutureFeature
+         */
+        public void onBackgroundScanAvailabilityChange(boolean isAvailable) {}
+
+        /**
+         * Called when a background scan completes successfully.
+         *
+         * @hide FutureFeature
+         */
+        public void onBackgroundScanComplete() {}
+
+        /**
+         * Called when available program list changed.
+         *
+         * Use getProgramList() to get the actual list.
+         *
+         * @hide FutureFeature
+         */
+        public void onProgramListChanged() {}
     }
 
 }
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 60b27b4..0448221 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -16,6 +16,7 @@
 package android.metrics;
 
 import android.annotation.SystemApi;
+import android.content.ComponentName;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -118,6 +119,16 @@
         return this;
     }
 
+    /**
+     * @param component to replace the existing setting.
+     * @hide
+     */
+    public LogMaker setComponentName(ComponentName component) {
+        entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, component.getPackageName());
+        entries.put(MetricsEvent.FIELD_CLASS_NAME, component.getClassName());
+        return this;
+    }
+
     /** Remove the package name property. */
     public LogMaker clearPackageName() {
         entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
diff --git a/core/java/android/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/os/Handler.java b/core/java/android/os/Handler.java
index 3c7c962..8678d95 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.Log;
 import android.util.Printer;
 
@@ -69,6 +71,7 @@
      */
     private static final boolean FIND_POTENTIAL_LEAKS = false;
     private static final String TAG = "Handler";
+    private static Handler MAIN_THREAD_HANDLER = null;
 
     /**
      * Callback interface you can use when instantiating a Handler to avoid
@@ -231,6 +234,21 @@
         mAsynchronous = async;
     }
 
+    /** @hide */
+    @NonNull
+    public static Handler getMain() {
+        if (MAIN_THREAD_HANDLER == null) {
+            MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
+        }
+        return MAIN_THREAD_HANDLER;
+    }
+
+    /** @hide */
+    @NonNull
+    public static Handler mainIfNull(@Nullable Handler handler) {
+        return handler == null ? getMain() : handler;
+    }
+
     /** {@hide} */
     public String getTraceName(Message message) {
         final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java
new file mode 100644
index 0000000..7dec4d7
--- /dev/null
+++ b/core/java/android/os/HidlSupport.java
@@ -0,0 +1,159 @@
+/*
+ * 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.os;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/** @hide */
+public class HidlSupport {
+    /**
+     * Similar to Objects.deepEquals, but also take care of lists.
+     * Two objects of HIDL types are considered equal if:
+     * 1. Both null
+     * 2. Both non-null, and of the same class, and:
+     * 2.1 Both are primitive arrays / enum arrays, elements are equal using == check
+     * 2.2 Both are object arrays, elements are checked recursively
+     * 2.3 Both are Lists, elements are checked recursively
+     * 2.4 (If both are collections other than lists or maps, throw an error)
+     * 2.5 lft.equals(rgt) returns true
+     */
+    public static boolean deepEquals(Object lft, Object rgt) {
+        if (lft == rgt) {
+            return true;
+        }
+        if (lft == null || rgt == null) {
+            return false;
+        }
+
+        Class<?> lftClazz = lft.getClass();
+        Class<?> rgtClazz = rgt.getClass();
+        if (lftClazz != rgtClazz) {
+            return false;
+        }
+
+        if (lftClazz.isArray()) {
+            Class<?> lftElementType = lftClazz.getComponentType();
+            if (lftElementType != rgtClazz.getComponentType()) {
+                return false;
+            }
+
+            if (lftElementType.isPrimitive()) {
+                return Objects.deepEquals(lft, rgt);
+            }
+
+            Object[] lftArray = (Object[])lft;
+            Object[] rgtArray = (Object[])rgt;
+            return (lftArray.length == rgtArray.length) &&
+                   IntStream.range(0, lftArray.length).allMatch(
+                        i -> deepEquals(lftArray[i], rgtArray[i]));
+        }
+
+        if (lft instanceof List<?>) {
+            List<Object> lftList = (List<Object>)lft;
+            List<Object> rgtList = (List<Object>)rgt;
+            if (lftList.size() != rgtList.size()) {
+                return false;
+            }
+
+            Iterator<Object> lftIter = lftList.iterator();
+            return rgtList.stream()
+                    .allMatch(rgtElement -> deepEquals(lftIter.next(), rgtElement));
+        }
+
+        throwErrorIfUnsupportedType(lft);
+
+        return lft.equals(rgt);
+    }
+
+    /**
+     * Similar to Arrays.deepHashCode, but also take care of lists.
+     */
+    public static int deepHashCode(Object o) {
+        if (o == null) {
+            return 0;
+        }
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray()) {
+            Class<?> elementType = clazz.getComponentType();
+            if (elementType.isPrimitive()) {
+                return primitiveArrayHashCode(o);
+            }
+            return Arrays.hashCode(Arrays.stream((Object[])o)
+                    .mapToInt(element -> deepHashCode(element))
+                    .toArray());
+        }
+
+        if (o instanceof List<?>) {
+            return Arrays.hashCode(((List<Object>)o).stream()
+                    .mapToInt(element -> deepHashCode(element))
+                    .toArray());
+        }
+
+        throwErrorIfUnsupportedType(o);
+
+        return o.hashCode();
+    }
+
+    private static void throwErrorIfUnsupportedType(Object o) {
+        if (o instanceof Collection<?> && !(o instanceof List<?>)) {
+            throw new UnsupportedOperationException(
+                    "Cannot check equality on collections other than lists: " +
+                    o.getClass().getName());
+        }
+
+        if (o instanceof Map<?, ?>) {
+            throw new UnsupportedOperationException(
+                    "Cannot check equality on maps");
+        }
+    }
+
+    private static int primitiveArrayHashCode(Object o) {
+        Class<?> elementType = o.getClass().getComponentType();
+        if (elementType == boolean.class) {
+            return Arrays.hashCode(((boolean[])o));
+        }
+        if (elementType == byte.class) {
+            return Arrays.hashCode(((byte[])o));
+        }
+        if (elementType == char.class) {
+            return Arrays.hashCode(((char[])o));
+        }
+        if (elementType == double.class) {
+            return Arrays.hashCode(((double[])o));
+        }
+        if (elementType == float.class) {
+            return Arrays.hashCode(((float[])o));
+        }
+        if (elementType == int.class) {
+            return Arrays.hashCode(((int[])o));
+        }
+        if (elementType == long.class) {
+            return Arrays.hashCode(((long[])o));
+        }
+        if (elementType == short.class) {
+            return Arrays.hashCode(((short[])o));
+        }
+        // Should not reach here.
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 63d3e7a..44dbcfb 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -21,6 +21,7 @@
 import android.os.LooperProto;
 import android.util.Log;
 import android.util.Printer;
+import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 /**
@@ -76,6 +77,9 @@
     private Printer mLogging;
     private long mTraceTag;
 
+    /* If set, the looper will show a warning log if a message dispatch takes longer than time. */
+    private long mSlowDispatchThresholdMs;
+
      /** Initialize the current thread as a looper.
       * This gives you a chance to create handlers that then reference
       * this looper, before actually starting the loop. Be sure to call
@@ -148,17 +152,30 @@
                         msg.callback + ": " + msg.what);
             }
 
+            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
+
             final long traceTag = me.mTraceTag;
             if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                 Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
             }
+            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
+            final long end;
             try {
                 msg.target.dispatchMessage(msg);
+                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
             } finally {
                 if (traceTag != 0) {
                     Trace.traceEnd(traceTag);
                 }
             }
+            if (slowDispatchThresholdMs > 0) {
+                final long time = end - start;
+                if (time > slowDispatchThresholdMs) {
+                    Slog.w(TAG, "Dispatch took " + time + "ms on "
+                            + Thread.currentThread().getName() + ", h=" +
+                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
+                }
+            }
 
             if (logging != null) {
                 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
@@ -226,6 +243,11 @@
         mTraceTag = traceTag;
     }
 
+    /** {@hide} */
+    public void setSlowDispatchThresholdMs(long slowDispatchThresholdMs) {
+        mSlowDispatchThresholdMs = slowDispatchThresholdMs;
+    }
+
     /**
      * Quits the looper.
      * <p>
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2e35a51..76128e6 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2039,14 +2039,20 @@
         }
     }
 
+    /** @deprecated use {@link android.system.Os#open(String, int, int)} */
+    @Deprecated
+    static native FileDescriptor openFileDescriptor(String file, int mode)
+            throws FileNotFoundException;
 
-    /*package*/ static native FileDescriptor openFileDescriptor(String file,
-            int mode) throws FileNotFoundException;
-    /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
-            throws IOException;
-    /*package*/ static native void closeFileDescriptor(FileDescriptor desc)
-            throws IOException;
-    /*package*/ static native void clearFileDescriptor(FileDescriptor desc);
+    /** @deprecated use {@link android.system.Os#dup(FileDescriptor)} */
+    @Deprecated
+    static native FileDescriptor dupFileDescriptor(FileDescriptor orig) throws IOException;
+
+    /** @deprecated use {@link android.system.Os#close(FileDescriptor)} */
+    @Deprecated
+    static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
+
+    static native void clearFileDescriptor(FileDescriptor desc);
 
     /**
      * Read a byte value from the parcel at the current dataPosition().
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 8882672..3212139 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -17,11 +17,21 @@
 package android.os;
 
 import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDONLY;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.O_TRUNC;
+import static android.system.OsConstants.O_WRONLY;
 import static android.system.OsConstants.SEEK_SET;
-import static android.system.OsConstants.SOCK_STREAM;
 import static android.system.OsConstants.SOCK_SEQPACKET;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.S_IROTH;
+import static android.system.OsConstants.S_IRWXG;
+import static android.system.OsConstants.S_IRWXU;
 import static android.system.OsConstants.S_ISLNK;
 import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_IWOTH;
 
 import android.content.BroadcastReceiver;
 import android.content.ContentProvider;
@@ -33,6 +43,7 @@
 import android.util.Log;
 
 import dalvik.system.CloseGuard;
+
 import libcore.io.IoUtils;
 import libcore.io.Memory;
 
@@ -279,8 +290,28 @@
                     "Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
         }
 
+        int flags = 0;
+        switch (mode & MODE_READ_WRITE) {
+            case 0:
+            case MODE_READ_ONLY: flags = O_RDONLY; break;
+            case MODE_WRITE_ONLY: flags = O_WRONLY; break;
+            case MODE_READ_WRITE: flags = O_RDWR; break;
+        }
+
+        if ((mode & MODE_CREATE) != 0) flags |= O_CREAT;
+        if ((mode & MODE_TRUNCATE) != 0) flags |= O_TRUNC;
+        if ((mode & MODE_APPEND) != 0) flags |= O_APPEND;
+
+        int realMode = S_IRWXU | S_IRWXG;
+        if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH;
+        if ((mode & MODE_WORLD_WRITEABLE) != 0) realMode |= S_IWOTH;
+
         final String path = file.getPath();
-        return Parcel.openFileDescriptor(path, mode);
+        try {
+            return Os.open(path, flags, realMode);
+        } catch (ErrnoException e) {
+            throw new FileNotFoundException(e.getMessage());
+        }
     }
 
     /**
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index ef5bc5c..5fa2461 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -483,7 +483,7 @@
             }
 
             final String filenameArg = "--update_package=" + filename + "\n";
-            final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
+            final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
             final String securityArg = "--security\n";
 
             String command = filenameArg + localeArg;
@@ -531,7 +531,7 @@
         }
 
         final String filenameArg = "--update_package=" + filename + "\n";
-        final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
+        final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
         final String securityArg = "--security\n";
 
         String command = filenameArg + localeArg;
@@ -647,7 +647,7 @@
             reasonArg = "--reason=" + sanitizeArg(reason);
         }
 
-        final String localeArg = "--locale=" + Locale.getDefault().toString();
+        final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
         bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
     }
 
@@ -678,7 +678,7 @@
             reasonArg = "--reason=" + sanitizeArg(reason);
         }
 
-        final String localeArg = "--locale=" + Locale.getDefault().toString();
+        final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
         bootCommand(context, "--wipe_cache", reasonArg, localeArg);
     }
 
@@ -703,7 +703,7 @@
 
         final String filename = packageFile.getCanonicalPath();
         final String filenameArg = "--wipe_package=" + filename;
-        final String localeArg = "--locale=" + Locale.getDefault().toString();
+        final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
         bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg);
     }
 
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index b3366d8..8208438 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -487,11 +487,11 @@
      * Instructs the zygote to pre-load the classes and native libraries at the given paths
      * for the specified abi. Not all zygotes support this function.
      */
-    public void preloadPackageForAbi(String packagePath, String libsPath, String abi)
-            throws ZygoteStartFailedEx, IOException {
+    public void preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
+                                     String abi) throws ZygoteStartFailedEx, IOException {
         synchronized(mLock) {
             ZygoteState state = openZygoteSocketIfNeeded(abi);
-            state.writer.write("3");
+            state.writer.write("4");
             state.writer.newLine();
 
             state.writer.write("--preload-package");
@@ -503,6 +503,9 @@
             state.writer.write(libsPath);
             state.writer.newLine();
 
+            state.writer.write(cacheKey);
+            state.writer.newLine();
+
             state.writer.flush();
         }
     }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 53c9a23..e5d73e0 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -59,6 +59,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.AppFuseMount;
 import com.android.internal.os.FuseAppLoop;
+import com.android.internal.os.FuseAppLoop.UnmountedException;
+import com.android.internal.os.FuseUnavailableMountException;
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
@@ -82,6 +84,7 @@
 import java.util.Objects;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicInteger;
+import libcore.io.IoUtils;
 
 /**
  * StorageManager is the interface to the systems storage service. The storage
@@ -113,6 +116,8 @@
     public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
     /** {@hide} */
     public static final String PROP_SDCARDFS = "persist.sys.sdcardfs";
+    /** {@hide} */
+    public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk";
 
     /** {@hide} */
     public static final String UUID_PRIVATE_INTERNAL = null;
@@ -140,6 +145,8 @@
     public static final int DEBUG_SDCARDFS_FORCE_ON = 1 << 2;
     /** {@hide} */
     public static final int DEBUG_SDCARDFS_FORCE_OFF = 1 << 3;
+    /** {@hide} */
+    public static final int DEBUG_VIRTUAL_DISK = 1 << 4;
 
     // NOTE: keep in sync with installd
     /** {@hide} */
@@ -1386,53 +1393,52 @@
     /** {@hide} */
     @VisibleForTesting
     public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
-            int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory)
+            int mode, ProxyFileDescriptorCallback callback, Handler handler, ThreadFactory factory)
                     throws IOException {
         MetricsLogger.count(mContext, "storage_open_proxy_file_descriptor", 1);
         // Retry is needed because the mount point mFuseAppLoop is using may be unmounted before
         // invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount
         // the bridge by calling mountProxyFileDescriptorBridge.
-        int retry = 3;
-        while (retry-- > 0) {
+        while (true) {
             try {
                 synchronized (mFuseAppLoopLock) {
+                    boolean newlyCreated = false;
                     if (mFuseAppLoop == null) {
                         final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge();
                         if (mount == null) {
-                            Log.e(TAG, "Failed to open proxy file bridge.");
-                            throw new IOException("Failed to open proxy file bridge.");
+                            throw new IOException("Failed to mount proxy bridge");
                         }
-                        mFuseAppLoop = FuseAppLoop.open(mount.mountPointId, mount.fd, factory);
+                        mFuseAppLoop = new FuseAppLoop(mount.mountPointId, mount.fd, factory);
+                        newlyCreated = true;
                     }
-
+                    if (handler == null) {
+                        handler = new Handler(Looper.getMainLooper());
+                    }
                     try {
-                        final int fileId = mFuseAppLoop.registerCallback(callback);
-                        final ParcelFileDescriptor pfd =
-                                mStorageManager.openProxyFileDescriptor(
-                                        mFuseAppLoop.getMountPointId(), fileId, mode);
-                        if (pfd != null) {
-                            return pfd;
+                        final int fileId = mFuseAppLoop.registerCallback(callback, handler);
+                        final ParcelFileDescriptor pfd = mStorageManager.openProxyFileDescriptor(
+                                mFuseAppLoop.getMountPointId(), fileId, mode);
+                        if (pfd == null) {
+                            mFuseAppLoop.unregisterCallback(fileId);
+                            throw new FuseUnavailableMountException(
+                                    mFuseAppLoop.getMountPointId());
                         }
-                        // Probably the bridge is being unmounted but mFuseAppLoop has not been
-                        // noticed it yet.
-                        mFuseAppLoop.unregisterCallback(fileId);
-                    } catch (FuseAppLoop.UnmountedException error) {
-                        Log.d(TAG, "mFuseAppLoop has been already unmounted.");
+                        return pfd;
+                    } catch (FuseUnavailableMountException exception) {
+                        // The bridge is being unmounted. Tried to recreate it unless the bridge was
+                        // just created.
+                        if (newlyCreated) {
+                            throw new IOException(exception);
+                        }
                         mFuseAppLoop = null;
                         continue;
                     }
                 }
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                    break;
-                }
             } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+                // Cannot recover from remote exception.
+                throw new IOException(e);
             }
         }
-
-        throw new IOException("Failed to mount bridge.");
     }
 
     /**
@@ -1444,16 +1450,37 @@
      *     {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
      *     {@link ParcelFileDescriptor#MODE_READ_WRITE}
      * @param callback Callback to process file operation requests issued on returned file
-     *     descriptor. The callback is invoked on a thread managed by the framework.
+     *     descriptor.
      * @return Seekable ParcelFileDescriptor.
      * @throws IOException
      */
     public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
             int mode, ProxyFileDescriptorCallback callback)
                     throws IOException {
-        return openProxyFileDescriptor(mode, callback, null);
+        return openProxyFileDescriptor(mode, callback, null, null);
     }
 
+    /**
+     * Opens seekable ParcelFileDescriptor that routes file operation requests to
+     * ProxyFileDescriptorCallback.
+     *
+     * @param mode The desired access mode, must be one of
+     *     {@link ParcelFileDescriptor#MODE_READ_ONLY},
+     *     {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
+     *     {@link ParcelFileDescriptor#MODE_READ_WRITE}
+     * @param callback Callback to process file operation requests issued on returned file
+     *     descriptor.
+     * @param handler Handler that invokes callback methods.
+     * @return Seekable ParcelFileDescriptor.
+     * @throws IOException
+     */
+    public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
+            int mode, ProxyFileDescriptorCallback callback, Handler handler)
+                    throws IOException {
+        return openProxyFileDescriptor(mode, callback, handler, null);
+    }
+
+
     /** {@hide} */
     @VisibleForTesting
     public int getProxyFileDescriptorMountPointId() {
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/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 2116847..8e01030 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -433,6 +433,7 @@
     public static final void requestListeningState(Context context, ComponentName component) {
         Intent intent = new Intent(ACTION_REQUEST_LISTENING);
         intent.putExtra(EXTRA_COMPONENT, component);
+        intent.setPackage("com.android.systemui");
         context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
     }
 }
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/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 70f9bdd..14d3ad7 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -22,13 +22,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.FontListParser;
+import android.net.Uri;
 import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
-import java.io.IOException;
 import java.lang.annotation.Retention;
-import java.util.Arrays;
 
 
 /**
@@ -44,20 +42,6 @@
     }
 
     /**
-     * For duplicating file descriptors.
-     *
-     * Note that this copy constructor can not be usable for deep copy.
-     * @hide
-     */
-    public FontConfig(@NonNull FontConfig config) {
-        mFamilies = new Family[config.mFamilies.length];
-        for (int i = 0; i < config.mFamilies.length; ++i) {
-            mFamilies[i] = new Family(config.mFamilies[i]);
-        }
-        mAliases = Arrays.copyOf(config.mAliases, config.mAliases.length);
-    }
-
-    /**
      * Returns the ordered list of families included in the system fonts.
      */
     public @NonNull Family[] getFamilies() {
@@ -174,7 +158,7 @@
         private final @NonNull Axis[] mAxes;
         private final int mWeight;
         private final boolean mIsItalic;
-        private @Nullable ParcelFileDescriptor mFd;
+        private Uri mUri;
 
         /**
          * @hide
@@ -186,29 +170,6 @@
             mAxes = axes;
             mWeight = weight;
             mIsItalic = isItalic;
-            mFd = null;
-        }
-
-        /**
-         * This is for duplicating FileDescriptors.
-         *
-         * Note that this copy ctor doesn't deep copy the members.
-         *
-         * @hide
-         */
-        public Font(Font origin) {
-            mFontName = origin.mFontName;
-            mTtcIndex = origin.mTtcIndex;
-            mAxes = origin.mAxes;
-            mWeight = origin.mWeight;
-            mIsItalic = origin.mIsItalic;
-            if (origin.mFd != null) {
-                try {
-                    mFd = origin.mFd.dup();
-                } catch (IOException e) {
-                    e.printStackTrace();
-                }
-            }
         }
 
         /**
@@ -247,17 +208,20 @@
         }
 
         /**
-         * Returns a file descriptor to access the specified font. This should be closed after use.
+         * Returns the content uri associated to this font.
+         *
+         * You can reach to the font contents by calling {@link
+         * android.content.ContentResolver#openInputStream}.
          */
-        public @Nullable ParcelFileDescriptor getFd() {
-            return mFd;
+        public @Nullable Uri getUri() {
+            return mUri;
         }
 
         /**
          * @hide
          */
-        public void setFd(@NonNull ParcelFileDescriptor fd) {
-            mFd = fd;
+        public void setUri(@NonNull Uri uri) {
+            mUri = uri;
         }
 
         /**
@@ -269,11 +233,7 @@
             mAxes = in.createTypedArray(Axis.CREATOR);
             mWeight = in.readInt();
             mIsItalic = in.readInt() == 1;
-            if (in.readInt() == 1) { /* has FD */
-                mFd = ParcelFileDescriptor.CREATOR.createFromParcel(in);
-            } else {
-                mFd = null;
-            }
+            mUri = in.readTypedObject(Uri.CREATOR);
         }
 
         @Override
@@ -283,10 +243,7 @@
             out.writeTypedArray(mAxes, flag);
             out.writeInt(mWeight);
             out.writeInt(mIsItalic ? 1 : 0);
-            out.writeInt(mFd == null ? 0 : 1);
-            if (mFd != null) {
-                mFd.writeToParcel(out, flag);
-            }
+            out.writeTypedObject(mUri, flag);
         }
 
         @Override
@@ -425,22 +382,6 @@
         }
 
         /**
-         * For duplicating file descriptor underlying Font object.
-         *
-         * This copy constructor is not for deep copying.
-         * @hide
-         */
-        public Family(Family origin) {
-            mName = origin.mName;
-            mLanguage = origin.mLanguage;
-            mVariant = origin.mVariant;
-            mFonts = new Font[origin.mFonts.length];
-            for (int i = 0; i < origin.mFonts.length; ++i) {
-                mFonts[i] = new Font(origin.mFonts[i]);
-            }
-        }
-
-        /**
          * Returns the name given by the system to this font family.
          */
         public @Nullable String getName() {
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index 87231e1..44019c32 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -17,6 +17,7 @@
 package android.util;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.ParcelableException;
 
 import com.android.internal.util.Preconditions;
@@ -55,10 +56,26 @@
         return getCompleteMessage(null, t);
     }
 
+    public static <E extends Throwable> void propagateIfInstanceOf(
+            @Nullable Throwable t, Class<E> c) throws E {
+        if (t != null && c.isInstance(t)) {
+            throw c.cast(t);
+        }
+    }
+
+    /**
+     * @param <E> a checked exception that is ok to throw without wrapping
+     */
+    public static <E extends Exception> RuntimeException propagate(@NonNull Throwable t, Class<E> c)
+            throws E {
+        propagateIfInstanceOf(t, c);
+        return propagate(t);
+    }
+
     public static RuntimeException propagate(@NonNull Throwable t) {
         Preconditions.checkNotNull(t);
-        if (t instanceof Error) throw (Error)t;
-        if (t instanceof RuntimeException) throw (RuntimeException)t;
+        propagateIfInstanceOf(t, Error.class);
+        propagateIfInstanceOf(t, RuntimeException.class);
         throw new RuntimeException(t);
     }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6c73b9b..e924f77 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -61,6 +61,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManagerGlobal;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -4170,14 +4171,14 @@
     /**
      * When this flag is used with {@link #DRAG_FLAG_GLOBAL}, the drag recipient will be able to
      * request read access to the content URI(s) contained in the {@link ClipData} object.
-     * @see android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
+     * @see android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION
      */
     public static final int DRAG_FLAG_GLOBAL_URI_READ = Intent.FLAG_GRANT_READ_URI_PERMISSION;
 
     /**
      * When this flag is used with {@link #DRAG_FLAG_GLOBAL}, the drag recipient will be able to
      * request write access to the content URI(s) contained in the {@link ClipData} object.
-     * @see android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+     * @see android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION
      */
     public static final int DRAG_FLAG_GLOBAL_URI_WRITE = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
 
@@ -4185,8 +4186,8 @@
      * When this flag is used with {@link #DRAG_FLAG_GLOBAL_URI_READ} and/or {@link
      * #DRAG_FLAG_GLOBAL_URI_WRITE}, the URI permission grant can be persisted across device
      * reboots until explicitly revoked with
-     * {@link android.content.Context#revokeUriPermission(Uri,int) Context.revokeUriPermission}.
-     * @see android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+     * {@link android.content.Context#revokeUriPermission(Uri, int)} Context.revokeUriPermission}.
+     * @see android.content.Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION
      */
     public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION =
             Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
@@ -4195,7 +4196,7 @@
      * When this flag is used with {@link #DRAG_FLAG_GLOBAL_URI_READ} and/or {@link
      * #DRAG_FLAG_GLOBAL_URI_WRITE}, the URI permission grant applies to any URI that is a prefix
      * match against the original granted URI.
-     * @see android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
+     * @see android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION
      */
     public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION =
             Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
@@ -7895,7 +7896,7 @@
      * @param arguments A {@link Bundle} holding any arguments relevant for this request. May be
      *                  {@code null} if the service provided no arguments.
      *
-     * @see AccessibilityNodeInfo#setExtraAvailableData
+     * @see AccessibilityNodeInfo#setAvailableExtraData(List)
      */
     public void addExtraDataToAccessibilityNodeInfo(
             @NonNull AccessibilityNodeInfo info, @NonNull String extraDataKey,
@@ -9839,7 +9840,7 @@
      * Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
      * window or serves as a target of cluster navigation.
      *
-     * @see #restoreDefaultFocus(int)
+     * @see #restoreDefaultFocus()
      *
      * @return {@code true} if this view is the default-focus view, {@code false} otherwise
      * @attr ref android.R.styleable#View_focusedByDefault
@@ -9859,7 +9860,7 @@
      * @param isFocusedByDefault {@code true} to set this view as the default-focus view,
      *                           {@code false} otherwise.
      *
-     * @see #restoreDefaultFocus(int)
+     * @see #restoreDefaultFocus()
      *
      * @attr ref android.R.styleable#View_focusedByDefault
      */
@@ -16457,29 +16458,34 @@
     }
 
     /**
-     * @see #onMovedToDisplay(int)
+     * @see #onMovedToDisplay(int, Configuration)
      */
-    void dispatchMovedToDisplay(Display display) {
+    void dispatchMovedToDisplay(Display display, Configuration config) {
         mAttachInfo.mDisplay = display;
         mAttachInfo.mDisplayState = display.getState();
-        onMovedToDisplay(display.getDisplayId());
+        onMovedToDisplay(display.getDisplayId(), config);
     }
 
     /**
      * Called by the system when the hosting activity is moved from one display to another without
      * recreation. This means that the activity is declared to handle all changes to configuration
      * that happened when it was switched to another display, so it wasn't destroyed and created
-     * again. This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
-     * applied configuration actually changed.
+     * again.
+     *
+     * <p>This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+     * applied configuration actually changed. It is up to app developer to choose whether to handle
+     * the change in this method or in the following {@link #onConfigurationChanged(Configuration)}
+     * call.
      *
      * <p>Use this callback to track changes to the displays if some functionality relies on an
      * association with some display properties.
      *
      * @param displayId The id of the display to which the view was moved.
+     * @param config Configuration of the resources on new display after move.
      *
      * @see #onConfigurationChanged(Configuration)
      */
-    public void onMovedToDisplay(int displayId) {
+    public void onMovedToDisplay(int displayId, Configuration config) {
     }
 
     /**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index de0ec40..7921938 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3281,13 +3281,13 @@
     }
 
     @Override
-    void dispatchMovedToDisplay(Display display) {
-        super.dispatchMovedToDisplay(display);
+    void dispatchMovedToDisplay(Display display, Configuration config) {
+        super.dispatchMovedToDisplay(display, config);
 
         final int count = mChildrenCount;
         final View[] children = mChildren;
         for (int i = 0; i < count; i++) {
-            children[i].dispatchMovedToDisplay(display);
+            children[i].dispatchMovedToDisplay(display, config);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1681787..cf52c60 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1106,10 +1106,11 @@
     /**
      * Notify about move to a different display.
      * @param displayId The id of the display where this view root is moved to.
+     * @param config Configuration of the resources on new display after move.
      *
      * @hide
      */
-    public void onMovedToDisplay(int displayId) {
+    public void onMovedToDisplay(int displayId, Configuration config) {
         if (mDisplay.getDisplayId() == displayId) {
             return;
         }
@@ -1120,7 +1121,7 @@
             mView.getResources());
         mAttachInfo.mDisplayState = mDisplay.getState();
         // Internal state updated, now notify the view hierarchy.
-        mView.dispatchMovedToDisplay(mDisplay);
+        mView.dispatchMovedToDisplay(mDisplay, config);
     }
 
     void pokeDrawLockIfNeeded() {
@@ -3485,15 +3486,16 @@
             mActivityConfigCallback.onConfigurationChanged(overrideConfig, newDisplayId);
         } else {
             // There is no activity callback - update the configuration right away.
-            updateConfiguration();
+            updateConfiguration(newDisplayId);
         }
         mForceNextConfigUpdate = false;
     }
 
     /**
      * Update display and views if last applied merged configuration changed.
+     * @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} otherwise.
      */
-    public void updateConfiguration() {
+    public void updateConfiguration(int newDisplayId) {
         if (mView == null) {
             return;
         }
@@ -3503,6 +3505,13 @@
         // the one in them which may be newer.
         final Resources localResources = mView.getResources();
         final Configuration config = localResources.getConfiguration();
+
+        // Handle move to display.
+        if (newDisplayId != INVALID_DISPLAY) {
+            onMovedToDisplay(newDisplayId, config);
+        }
+
+        // Handle configuration change.
         if (mForceNextConfigUpdate || mLastConfigurationFromResources.diff(config) != 0) {
             // Update the display with new DisplayAdjustments.
             mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(
@@ -3674,15 +3683,17 @@
                     SomeArgs args = (SomeArgs) msg.obj;
 
                     final int displayId = args.argi3;
-                    final boolean displayChanged = mDisplay.getDisplayId() != displayId;
-                    if (displayChanged) {
-                        onMovedToDisplay(displayId);
-                    }
-
                     final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
+                    final boolean displayChanged = mDisplay.getDisplayId() != displayId;
+
                     if (mergedConfiguration != null) {
+                        // If configuration changed - notify about that and, maybe, about move to
+                        // display.
                         performConfigurationChange(mergedConfiguration, false /* force */,
                                 displayChanged ? displayId : INVALID_DISPLAY /* same display */);
+                    } else if (displayChanged) {
+                        // Moved to display without config change - report last applied one.
+                        onMovedToDisplay(displayId, mLastConfigurationFromResources);
                     }
 
                     final boolean framesChanged = !mWinFrame.equals(args.arg1)
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 44309a7..28c2d01 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -520,27 +520,27 @@
     }
 
     private HashMap<String, String> getExtraValueHashMap() {
-        if (mExtraValueHashMapCache == null) {
-            synchronized(this) {
-                if (mExtraValueHashMapCache == null) {
-                    mExtraValueHashMapCache = new HashMap<String, String>();
-                    final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
-                    final int N = pairs.length;
-                    for (int i = 0; i < N; ++i) {
-                        final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
-                        if (pair.length == 1) {
-                            mExtraValueHashMapCache.put(pair[0], null);
-                        } else if (pair.length > 1) {
-                            if (pair.length > 2) {
-                                Slog.w(TAG, "ExtraValue has two or more '='s");
-                            }
-                            mExtraValueHashMapCache.put(pair[0], pair[1]);
-                        }
+        synchronized (this) {
+            HashMap<String, String> extraValueMap = mExtraValueHashMapCache;
+            if (extraValueMap != null) {
+                return extraValueMap;
+            }
+            extraValueMap = new HashMap<>();
+            final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
+            for (int i = 0; i < pairs.length; ++i) {
+                final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
+                if (pair.length == 1) {
+                    extraValueMap.put(pair[0], null);
+                } else if (pair.length > 1) {
+                    if (pair.length > 2) {
+                        Slog.w(TAG, "ExtraValue has two or more '='s");
                     }
+                    extraValueMap.put(pair[0], pair[1]);
                 }
             }
+            mExtraValueHashMapCache = extraValueMap;
+            return extraValueMap;
         }
-        return mExtraValueHashMapCache;
     }
 
     /**
diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
index 9397a41..f0f39b6 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,8 +64,8 @@
      * scores for different collections.
      */
     public ClassificationResult[] classifyText(
-            String context, int selectionBegin, int selectionEnd) {
-        return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd);
+            String context, int selectionBegin, int selectionEnd, int hintFlags) {
+        return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd, hintFlags);
     }
 
     /**
@@ -76,7 +81,7 @@
             long context, String text, int selectionBegin, int selectionEnd);
 
     private static native ClassificationResult[] nativeClassifyText(
-            long context, String text, int selectionBegin, int selectionEnd);
+            long context, String text, int selectionBegin, int selectionEnd, int hintFlags);
 
     private static native void nativeClose(long context);
 
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 46f7a81..dabbf31 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -70,26 +70,6 @@
         public LinksInfo getLinks(CharSequence text, int linkMask, LocaleList defaultLocales) {
             return LinksInfo.NO_OP;
         }
-
-        // TODO: Remove
-        @Override
-        public TextSelection suggestSelection(
-                CharSequence text, int selectionStartIndex, int selectionEndIndex) {
-            throw new UnsupportedOperationException("Removed");
-        }
-
-        // TODO: Remove
-        @Override
-        public TextClassificationResult getTextClassificationResult(
-                CharSequence text, int startIndex, int endIndex) {
-            throw new UnsupportedOperationException("Removed");
-        }
-
-        // TODO: Remove
-        @Override
-        public LinksInfo getLinks(CharSequence text, int linkMask) {
-            throw new UnsupportedOperationException("Removed");
-        }
     };
 
     /**
@@ -154,16 +134,4 @@
      */
     LinksInfo getLinks(
             @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales);
-
-    // TODO: Remove
-    /** @removed */
-    TextSelection suggestSelection(
-            CharSequence text, int selectionStartIndex, int selectionEndIndex);
-    // TODO: Remove
-    /** @removed */
-    TextClassificationResult getTextClassificationResult(
-            CharSequence text, int startIndex, int endIndex);
-    // TODO: Remove
-    /** @removed */
-    LinksInfo getLinks(CharSequence text, int linkMask);
 }
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 06ac869..66a62c3 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -35,6 +35,7 @@
 import android.text.style.ClickableSpan;
 import android.text.util.Linkify;
 import android.util.Log;
+import android.util.Patterns;
 import android.view.View;
 
 import com.android.internal.util.Preconditions;
@@ -88,7 +89,9 @@
                 if (start >= 0 && end <= string.length() && start <= end) {
                     final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end);
                     final SmartSelection.ClassificationResult[] results =
-                            getSmartSelection().classifyText(string, start, end);
+                            getSmartSelection().classifyText(
+                                    string, start, end,
+                                    getHintFlags(string, start, end));
                     final int size = results.length;
                     for (int i = 0; i < size; i++) {
                         tsBuilder.setEntityType(results[i].mCollection, results[i].mScore);
@@ -116,14 +119,18 @@
         validateInput(text, startIndex, endIndex);
         try {
             if (text.length() > 0) {
-                final CharSequence classified = text.subSequence(startIndex, endIndex);
+                final String string = text.toString();
                 SmartSelection.ClassificationResult[] results = getSmartSelection()
-                        .classifyText(text.toString(), startIndex, endIndex);
+                        .classifyText(string, startIndex, endIndex,
+                                getHintFlags(string, startIndex, endIndex));
                 if (results.length > 0) {
+                    final TextClassificationResult classificationResult =
+                            createClassificationResult(
+                                    results, string.subSequence(startIndex, endIndex));
                     // TODO: Added this log for debug only. Remove before release.
                     Log.d(LOG_TAG, String.format(
-                            "Classification type: %s", getHighestScoringType(results)));
-                    return createClassificationResult(results, classified);
+                            "Classification type: %s", classificationResult));
+                    return classificationResult;
                 }
             }
         } catch (Throwable t) {
@@ -149,26 +156,6 @@
         return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
     }
 
-    // TODO: Remove
-    @Override
-    public TextSelection suggestSelection(
-            CharSequence text, int selectionStartIndex, int selectionEndIndex) {
-        throw new UnsupportedOperationException("Removed");
-    }
-
-    // TODO: Remove
-    @Override
-    public TextClassificationResult getTextClassificationResult(
-            CharSequence text, int startIndex, int endIndex) {
-        throw new UnsupportedOperationException("Removed");
-    }
-
-    // TODO: Remove
-    @Override
-    public LinksInfo getLinks(CharSequence text, int linkMask) {
-        throw new UnsupportedOperationException("Removed");
-    }
-
     private SmartSelection getSmartSelection() throws FileNotFoundException {
         synchronized (mSmartSelectionLock) {
             if (mSmartSelection == null) {
@@ -226,6 +213,24 @@
         return builder.build();
     }
 
+    private static int getHintFlags(CharSequence text, int start, int end) {
+        int flag = 0;
+        final CharSequence subText = text.subSequence(start, end);
+        if (Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(subText).matches()) {
+            flag |= SmartSelection.HINT_FLAG_EMAIL;
+        }
+        if (Patterns.AUTOLINK_WEB_URL.matcher(subText).matches()
+                && Linkify.sUrlMatchFilter.acceptMatch(text, start, end)) {
+            flag |= SmartSelection.HINT_FLAG_URL;
+        }
+        // TODO: Added this log for debug only. Remove before release.
+        Log.d(LOG_TAG, String.format("Email hint: %b",
+                (flag & SmartSelection.HINT_FLAG_EMAIL) != 0));
+        Log.d(LOG_TAG, String.format("Url hint: %b",
+                (flag & SmartSelection.HINT_FLAG_URL) != 0));
+        return flag;
+    }
+
     private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) {
         if (types.length < 1) {
             return "";
@@ -280,7 +285,9 @@
                 if (selectionStart >= 0 && selectionEnd <= text.length()
                         && selectionStart <= selectionEnd) {
                     final SmartSelection.ClassificationResult[] results =
-                            smartSelection.classifyText(text, selectionStart, selectionEnd);
+                            smartSelection.classifyText(
+                                    text, selectionStart, selectionEnd,
+                                    getHintFlags(text, selectionStart, selectionEnd));
                     if (results.length > 0) {
                         final String type = getHighestScoringType(results);
                         if (matches(type, linkMask)) {
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 81c2f5d..71db6b1 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -280,6 +280,44 @@
         }
     }
 
+    /**
+     * If the ApplicationInfo provided is for a stub WebView, fix up the object to include the
+     * required values from the donor package. If the ApplicationInfo is for a full WebView,
+     * leave it alone. Throws MissingWebViewPackageException if the donor is missing.
+     */
+    private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm) {
+        String donorPackageName = null;
+        if (ai.metaData != null) {
+            donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage");
+        }
+        if (donorPackageName != null) {
+            PackageInfo donorPackage;
+            try {
+                donorPackage = pm.getPackageInfo(
+                        donorPackageName,
+                        PackageManager.GET_SHARED_LIBRARY_FILES
+                        | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
+                        | PackageManager.MATCH_UNINSTALLED_PACKAGES
+                        | PackageManager.MATCH_FACTORY_ONLY);
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new MissingWebViewPackageException("Failed to find donor package: " +
+                                                         donorPackageName);
+            }
+            ApplicationInfo donorInfo = donorPackage.applicationInfo;
+
+            // Replace the stub's code locations with the donor's.
+            ai.sourceDir = donorInfo.sourceDir;
+            ai.splitSourceDirs = donorInfo.splitSourceDirs;
+            ai.nativeLibraryDir = donorInfo.nativeLibraryDir;
+            ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir;
+
+            // Copy the donor's primary and secondary ABIs, since the stub doesn't have native code
+            // and so they are unset.
+            ai.primaryCpuAbi = donorInfo.primaryCpuAbi;
+            ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi;
+        }
+    }
+
     private static Context getWebViewContextAndSetProvider() {
         Application initialApplication = AppGlobals.getInitialApplication();
         try {
@@ -307,9 +345,10 @@
             }
             // Fetch package info and verify it against the chosen package
             PackageInfo newPackageInfo = null;
+            PackageManager pm = initialApplication.getPackageManager();
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "PackageManager.getPackageInfo()");
             try {
-                newPackageInfo = initialApplication.getPackageManager().getPackageInfo(
+                newPackageInfo = pm.getPackageInfo(
                     response.packageInfo.packageName,
                     PackageManager.GET_SHARED_LIBRARY_FILES
                     | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
@@ -328,12 +367,15 @@
             // failure
             verifyPackageInfo(response.packageInfo, newPackageInfo);
 
+            ApplicationInfo ai = newPackageInfo.applicationInfo;
+            fixupStubApplicationInfo(ai, pm);
+
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                     "initialApplication.createApplicationContext");
             try {
                 // Construct an app context to load the Java code into the current app.
                 Context webViewContext = initialApplication.createApplicationContext(
-                        newPackageInfo.applicationInfo,
+                        ai,
                         Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
                 sPackageInfo = newPackageInfo;
                 return webViewContext;
@@ -449,7 +491,11 @@
      */
     public static int onWebViewProviderChanged(PackageInfo packageInfo) {
         String[] nativeLibs = null;
+        String originalSourceDir = packageInfo.applicationInfo.sourceDir;
         try {
+            fixupStubApplicationInfo(packageInfo.applicationInfo,
+                                     AppGlobals.getInitialApplication().getPackageManager());
+
             nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths(packageInfo);
             if (nativeLibs != null) {
                 long newVmSize = 0L;
@@ -498,7 +544,7 @@
             Log.e(LOGTAG, "error preparing webview native library", t);
         }
 
-        WebViewZygote.onWebViewProviderChanged(packageInfo);
+        WebViewZygote.onWebViewProviderChanged(packageInfo, originalSourceDir);
 
         return prepareWebViewInSystemServer(nativeLibs);
     }
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index f78d622..2123deb 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -67,6 +67,13 @@
     private static PackageInfo sPackage;
 
     /**
+     * Cache key for the selected WebView package's classloader. This is set from
+     * #onWebViewProviderChanged().
+     */
+    @GuardedBy("sLock")
+    private static String sPackageCacheKey;
+
+    /**
      * Flag for whether multi-process WebView is enabled. If this is false, the zygote
      * will not be started.
      */
@@ -118,9 +125,10 @@
         }
     }
 
-    public static void onWebViewProviderChanged(PackageInfo packageInfo) {
+    public static void onWebViewProviderChanged(PackageInfo packageInfo, String cacheKey) {
         synchronized (sLock) {
             sPackage = packageInfo;
+            sPackageCacheKey = cacheKey;
 
             // If multi-process is not enabled, then do not start the zygote service.
             if (!sMultiprocessEnabled) {
@@ -210,7 +218,8 @@
                     TextUtils.join(File.pathSeparator, zipPaths);
 
             Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
-            sZygote.preloadPackageForAbi(zip, librarySearchPath, Build.SUPPORTED_ABIS[0]);
+            sZygote.preloadPackageForAbi(zip, librarySearchPath, sPackageCacheKey,
+                                         Build.SUPPORTED_ABIS[0]);
         } catch (Exception e) {
             Log.e(LOGTAG, "Error connecting to " + serviceName, e);
             sZygote = null;
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1937187..99b91bd 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -6873,9 +6873,11 @@
                     mTransientStateViews.put(position, scrap);
                 } else {
                     // Otherwise, we'll have to remove the view and start over.
+                    clearScrapForRebind(scrap);
                     getSkippedScrap().add(scrap);
                 }
             } else {
+                clearScrapForRebind(scrap);
                 if (mViewTypeCount == 1) {
                     mCurrentScrap.add(scrap);
                 } else {
@@ -7098,12 +7100,12 @@
                         }
                     } else if (params.scrappedFromPosition == position) {
                         final View scrap = scrapViews.remove(i);
-                        clearAccessibilityFromScrap(scrap);
+                        clearScrapForRebind(scrap);
                         return scrap;
                     }
                 }
                 final View scrap = scrapViews.remove(size - 1);
-                clearAccessibilityFromScrap(scrap);
+                clearScrapForRebind(scrap);
                 return scrap;
             } else {
                 return null;
@@ -7117,7 +7119,7 @@
             }
         }
 
-        private void clearAccessibilityFromScrap(View view) {
+        private void clearScrapForRebind(View view) {
             view.clearAccessibilityFocus();
             view.setAccessibilityDelegate(null);
         }
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 78d18fd..ab4cce4 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -523,9 +523,17 @@
     /**
      * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
      *
-     * @param height Height of the popup window.
+     * @param height Height of the popup window must be a positive value,
+     *               {@link #MATCH_PARENT}, or {@link #WRAP_CONTENT}.
+     *
+     * @throws IllegalArgumentException if height is set to negative value
      */
     public void setHeight(int height) {
+        if (height < 0 && ViewGroup.LayoutParams.WRAP_CONTENT != height
+                && ViewGroup.LayoutParams.MATCH_PARENT != height) {
+            throw new IllegalArgumentException(
+                   "Invalid height. Must be a positive value, MATCH_PARENT, or WRAP_CONTENT.");
+        }
         mDropDownHeight = height;
     }
 
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 1c0c4ef..12e35a1 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1639,7 +1639,7 @@
                     final View focusChild = getAccessibilityFocusedChild(focusHost);
                     if (focusChild != null) {
                         if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
-                                || focusChild.hasTransientState() || mAdapterHasStableIds) {
+                                || (focusChild.hasTransientState() && mAdapterHasStableIds)) {
                             // The views won't be changing, so try to maintain
                             // focus on the current host and virtual view.
                             accessibilityFocusLayoutRestoreView = focusHost;
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index b63b899..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);
         }
     }
 
@@ -2178,9 +2178,14 @@
             update = true;
         }
 
-        final View anchor = mAnchor.get();
-        final int newAccessibilityIdOfAnchor = (anchor != null)
-                ? anchor.getAccessibilityViewId() : -1;
+        View anchor = null;
+        int newAccessibilityIdOfAnchor = -1;
+
+        if (mAnchor != null && mAnchor.get() != null) {
+            anchor = mAnchor.get();
+            newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+        }
+
         if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
             p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
             update = true;
@@ -2366,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);
@@ -2477,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,
@@ -2493,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) {
@@ -2539,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/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 7ce5fc3..b3904f4 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -477,12 +477,9 @@
             final KeyCharacterMap kmap = KeyCharacterMap.load(
                     event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
             menu.setQwertyMode(kmap.getKeyboardType() != KeyCharacterMap.NUMERIC);
-            menu.performShortcut(keyCode, event, 0);
+            return menu.performShortcut(keyCode, event, 0);
         }
-        // This action bar always returns true for handling keyboard shortcuts.
-        // This will block the window from preparing a temporary panel to handle
-        // keyboard shortcuts.
-        return true;
+        return false;
     }
 
     @Override
@@ -525,6 +522,17 @@
             }
             return result;
         }
+
+        @Override
+        public View onCreatePanelView(int featureId) {
+            if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+                // This gets called by PhoneWindow.preparePanel. Since this already manages
+                // its own panel, we return a dummy view here to prevent PhoneWindow from
+                // preparing a default one.
+                return new View(mDecorToolbar.getContext());
+            }
+            return super.onCreatePanelView(featureId);
+        }
     }
 
     private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index 3603b6d..8edd637 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -19,16 +19,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.ProxyFileDescriptorCallback;
+import android.os.Handler;
 import android.os.ParcelFileDescriptor;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.util.Log;
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
-
-import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.ThreadFactory;
 
 public class FuseAppLoop {
@@ -42,14 +42,21 @@
             return new Thread(r, TAG);
         }
     };
+    private static final int FUSE_OK = 0;
 
     private final Object mLock = new Object();
     private final int mMountPointId;
     private final Thread mThread;
+    private final Handler mDefaultHandler;
+
+    private static final int CMD_FSYNC = 1;
 
     @GuardedBy("mLock")
     private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
 
+    @GuardedBy("mLock")
+    private final BytesMap mBytesMap = new BytesMap();
+
     /**
      * Sequential number can be used as file name and inode in AppFuse.
      * 0 is regarded as an error, 1 is mount point. So we start the number from 2.
@@ -57,38 +64,40 @@
     @GuardedBy("mLock")
     private int mNextInode = MIN_INODE;
 
-    private FuseAppLoop(
+    @GuardedBy("mLock")
+    private long mInstance;
+
+    public FuseAppLoop(
             int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
         mMountPointId = mountPointId;
-        final int rawFd = fd.detachFd();
         if (factory == null) {
             factory = sDefaultThreadFactory;
         }
-        mThread = factory.newThread(new Runnable() {
-            @Override
-            public void run() {
-                // rawFd is closed by native_start_loop. Java code does not need to close it.
-                native_start_loop(rawFd);
+        mInstance = native_new(fd.detachFd());
+        mThread = factory.newThread(() -> {
+            native_start(mInstance);
+            synchronized (mLock) {
+                native_delete(mInstance);
+                mInstance = 0;
+                mBytesMap.clear();
             }
         });
+        mThread.start();
+        mDefaultHandler = null;
     }
 
-    public static @NonNull FuseAppLoop open(int mountPointId, @NonNull ParcelFileDescriptor fd,
-            @Nullable ThreadFactory factory) {
-        Preconditions.checkNotNull(fd);
-        final FuseAppLoop loop = new FuseAppLoop(mountPointId, fd, factory);
-        loop.mThread.start();
-        return loop;
-    }
-
-    public int registerCallback(@NonNull ProxyFileDescriptorCallback callback)
-            throws UnmountedException, IOException {
-        if (mThread.getState() == Thread.State.TERMINATED) {
-            throw new UnmountedException();
-        }
+    public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
+            @NonNull Handler handler) throws FuseUnavailableMountException {
         synchronized (mLock) {
-            if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) {
-                throw new IOException("Too many opened files.");
+            Preconditions.checkNotNull(callback);
+            Preconditions.checkNotNull(handler);
+            Preconditions.checkState(
+                    mCallbackMap.size() < Integer.MAX_VALUE - MIN_INODE, "Too many opened files.");
+            Preconditions.checkArgument(
+                    Thread.currentThread().getId() != handler.getLooper().getThread().getId(),
+                    "Handler must be different from the current thread");
+            if (mInstance == 0) {
+                throw new FuseUnavailableMountException(mMountPointId);
             }
             int id;
             while (true) {
@@ -101,118 +110,171 @@
                     break;
                 }
             }
-            mCallbackMap.put(id, new CallbackEntry(callback));
+            mCallbackMap.put(id, new CallbackEntry(callback, handler));
             return id;
         }
     }
 
     public void unregisterCallback(int id) {
-        mCallbackMap.remove(id);
+        synchronized (mLock) {
+            mCallbackMap.remove(id);
+        }
     }
 
     public int getMountPointId() {
         return mMountPointId;
     }
 
-    private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
-        final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
-        if (entry != null) {
-            return entry;
-        } else {
-            throw new ErrnoException("getCallbackEntry", OsConstants.ENOENT);
-        }
-    }
+    // Defined in fuse.h
+    private static final int FUSE_LOOKUP = 1;
+    private static final int FUSE_GETATTR = 3;
+    private static final int FUSE_OPEN = 14;
+    private static final int FUSE_READ = 15;
+    private static final int FUSE_WRITE = 16;
+    private static final int FUSE_RELEASE = 18;
+    private static final int FUSE_FSYNC = 20;
+
+    // Defined in FuseBuffer.h
+    private static final int FUSE_MAX_WRITE = 256 * 1024;
 
     // Called by JNI.
     @SuppressWarnings("unused")
-    private long onGetSize(long inode) {
+    private void onCommand(int command, long unique, long inode, long offset, int size,
+            byte[] data) {
         synchronized(mLock) {
             try {
-                return getCallbackEntryOrThrowLocked(inode).callback.onGetSize();
-            } catch (ErrnoException exp) {
-                return getError(exp);
+                final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
+                entry.postRunnable(() -> {
+                    try {
+                        switch (command) {
+                            case FUSE_LOOKUP: {
+                                final long fileSize = entry.callback.onGetSize();
+                                synchronized (mLock) {
+                                    if (mInstance != 0) {
+                                        native_replyLookup(mInstance, unique, inode, fileSize);
+                                    }
+                                }
+                                break;
+                            }
+                            case FUSE_GETATTR: {
+                                final long fileSize = entry.callback.onGetSize();
+                                synchronized (mLock) {
+                                    if (mInstance != 0) {
+                                        native_replyGetAttr(mInstance, unique, inode, fileSize);
+                                    }
+                                }
+                                break;
+                            }
+                            case FUSE_READ:
+                                final int readSize = entry.callback.onRead(offset, size, data);
+                                synchronized (mLock) {
+                                    if (mInstance != 0) {
+                                        native_replyRead(mInstance, unique, readSize, data);
+                                    }
+                                }
+                                break;
+                            case FUSE_WRITE:
+                                final int writeSize = entry.callback.onWrite(offset, size, data);
+                                synchronized (mLock) {
+                                    if (mInstance != 0) {
+                                        native_replyWrite(mInstance, unique, writeSize);
+                                    }
+                                }
+                                break;
+                            case FUSE_FSYNC:
+                                entry.callback.onFsync();
+                                synchronized (mLock) {
+                                    if (mInstance != 0) {
+                                        native_replySimple(mInstance, unique, FUSE_OK);
+                                    }
+                                }
+                                break;
+                            case FUSE_RELEASE:
+                                entry.callback.onRelease();
+                                synchronized (mLock) {
+                                    if (mInstance != 0) {
+                                        native_replySimple(mInstance, unique, FUSE_OK);
+                                    }
+                                    mBytesMap.stopUsing(entry.getThreadId());
+                                }
+                                break;
+                            default:
+                                throw new IllegalArgumentException(
+                                        "Unknown FUSE command: " + command);
+                        }
+                    } catch (Exception error) {
+                        Log.e(TAG, "", error);
+                        replySimple(unique, getError(error));
+                    }
+                });
+            } catch (ErrnoException error) {
+                Log.e(TAG, "", error);
+                replySimpleLocked(unique, getError(error));
             }
         }
     }
 
     // Called by JNI.
     @SuppressWarnings("unused")
-    private int onOpen(long inode) {
-        synchronized(mLock) {
+    private byte[] onOpen(long unique, long inode) {
+        synchronized (mLock) {
             try {
                 final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
                 if (entry.opened) {
                     throw new ErrnoException("onOpen", OsConstants.EMFILE);
                 }
-                entry.opened = true;
-                // Use inode as file handle. It's OK because AppFuse does not allow to open the same
-                // file twice.
-                return (int) inode;
-            } catch (ErrnoException exp) {
-                return getError(exp);
+                if (mInstance != 0) {
+                    native_replyOpen(mInstance, unique, /* fh */ inode);
+                    entry.opened = true;
+                    return mBytesMap.startUsing(entry.getThreadId());
+                }
+            } catch (ErrnoException error) {
+                replySimpleLocked(unique, getError(error));
             }
+            return null;
         }
     }
 
-    // Called by JNI.
-    @SuppressWarnings("unused")
-    private int onFsync(long inode) {
-        synchronized(mLock) {
-            try {
-                getCallbackEntryOrThrowLocked(inode).callback.onFsync();
-                return 0;
-            } catch (ErrnoException exp) {
-                return getError(exp);
+    private static int getError(@NonNull Exception error) {
+        if (error instanceof ErrnoException) {
+            final int errno = ((ErrnoException) error).errno;
+            if (errno != OsConstants.ENOSYS) {
+                return -errno;
             }
         }
+        return -OsConstants.EBADF;
+    }
+
+    private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
+        final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
+        if (entry == null) {
+            throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT);
+        }
+        return entry;
+    }
+
+    private void replySimple(long unique, int result) {
+        synchronized (mLock) {
+            replySimpleLocked(unique, result);
+        }
     }
 
-    // Called by JNI.
-    @SuppressWarnings("unused")
-    private int onRelease(long inode) {
-        synchronized(mLock) {
-            try {
-                getCallbackEntryOrThrowLocked(inode).callback.onRelease();
-                return 0;
-            } catch (ErrnoException exp) {
-                return getError(exp);
-            } finally {
-                mCallbackMap.remove(checkInode(inode));
-            }
+    private void replySimpleLocked(long unique, int result) {
+        if (mInstance != 0) {
+            native_replySimple(mInstance, unique, result);
         }
     }
 
-    // Called by JNI.
-    @SuppressWarnings("unused")
-    private int onRead(long inode, long offset, int size, byte[] bytes) {
-        synchronized(mLock) {
-            try {
-                return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes);
-            } catch (ErrnoException exp) {
-                return getError(exp);
-            }
-        }
-    }
+    native long native_new(int fd);
+    native void native_delete(long ptr);
+    native void native_start(long ptr);
 
-    // Called by JNI.
-    @SuppressWarnings("unused")
-    private int onWrite(long inode, long offset, int size, byte[] bytes) {
-        synchronized(mLock) {
-            try {
-                return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes);
-            } catch (ErrnoException exp) {
-                return getError(exp);
-            }
-        }
-    }
-
-    private static int getError(@NonNull ErrnoException exp) {
-        // Should not return ENOSYS because the kernel stops
-        // dispatching the FUSE action once FUSE implementation returns ENOSYS for the action.
-        return exp.errno != OsConstants.ENOSYS ? -exp.errno : -OsConstants.EIO;
-    }
-
-    native boolean native_start_loop(int fd);
+    native void native_replySimple(long ptr, long unique, int result);
+    native void native_replyOpen(long ptr, long unique, long fh);
+    native void native_replyLookup(long ptr, long unique, long inode, long size);
+    native void native_replyGetAttr(long ptr, long unique, long inode, long size);
+    native void native_replyWrite(long ptr, long unique, int size);
+    native void native_replyRead(long ptr, long unique, int size, byte[] bytes);
 
     private static int checkInode(long inode) {
         Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode");
@@ -223,10 +285,61 @@
 
     private static class CallbackEntry {
         final ProxyFileDescriptorCallback callback;
+        final Handler handler;
         boolean opened;
-        CallbackEntry(ProxyFileDescriptorCallback callback) {
-            Preconditions.checkNotNull(callback);
-            this.callback = callback;
+
+        CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) {
+            this.callback = Preconditions.checkNotNull(callback);
+            this.handler = Preconditions.checkNotNull(handler);
+        }
+
+        long getThreadId() {
+            return handler.getLooper().getThread().getId();
+        }
+
+        void postRunnable(Runnable runnable) throws ErrnoException {
+            final boolean result = handler.post(runnable);
+            if (!result) {
+                throw new ErrnoException("postRunnable", OsConstants.EBADF);
+            }
+        }
+    }
+
+    /**
+     * Entry for bytes map.
+     */
+    private static class BytesMapEntry {
+        int counter = 0;
+        byte[] bytes = new byte[FUSE_MAX_WRITE];
+    }
+
+    /**
+     * Map between Thread ID and byte buffer.
+     */
+    private static class BytesMap {
+        final Map<Long, BytesMapEntry> mEntries = new HashMap<>();
+
+        byte[] startUsing(long threadId) {
+            BytesMapEntry entry = mEntries.get(threadId);
+            if (entry == null) {
+                entry = new BytesMapEntry();
+                mEntries.put(threadId, entry);
+            }
+            entry.counter++;
+            return entry.bytes;
+        }
+
+        void stopUsing(long threadId) {
+            final BytesMapEntry entry = mEntries.get(threadId);
+            Preconditions.checkNotNull(entry);
+            entry.counter--;
+            if (entry.counter <= 0) {
+                mEntries.remove(threadId);
+            }
+        }
+
+        void clear() {
+            mEntries.clear();
         }
     }
 }
diff --git a/media/java/android/media/UnsupportedCasException.java b/core/java/com/android/internal/os/FuseUnavailableMountException.java
similarity index 66%
rename from media/java/android/media/UnsupportedCasException.java
rename to core/java/com/android/internal/os/FuseUnavailableMountException.java
index 3167637..ca3cfb9 100644
--- a/media/java/android/media/UnsupportedCasException.java
+++ b/core/java/com/android/internal/os/FuseUnavailableMountException.java
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package android.media;
+package com.android.internal.os;
 
 /**
- * Exception thrown when an attempt is made to construct a MediaCas object
- * using a CA_system_id that is not supported by the device
+ * Exception occurred when the mount point has already been unavailable.
  */
-public final class UnsupportedCasException extends MediaCasException {
-    public UnsupportedCasException(String detailMessage) {
-        super(detailMessage);
+public class FuseUnavailableMountException extends Exception {
+    public FuseUnavailableMountException(int mountId) {
+        super("AppFuse mount point " + mountId + " is unavailable");
     }
 }
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index f27c0d4..cc3f58c 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -26,6 +26,7 @@
 import android.webkit.WebViewFactory;
 import android.webkit.WebViewFactoryProvider;
 
+import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 
@@ -67,16 +68,20 @@
         }
 
         @Override
-        protected boolean handlePreloadPackage(String packagePath, String libsPath) {
+        protected boolean handlePreloadPackage(String packagePath, String libsPath,
+                                               String cacheKey) {
             // Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
             // our children will reuse the same classloader instead of creating their own.
             // This enables us to preload Java and native code in the webview zygote process and
             // have the preloaded versions actually be used post-fork.
             ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
-                    packagePath, libsPath);
+                    packagePath, libsPath, cacheKey);
 
             // Add the APK to the Zygote's list of allowed files for children.
-            Zygote.nativeAllowFileAcrossFork(packagePath);
+            String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
+            for (String packageEntry : packageList) {
+                Zygote.nativeAllowFileAcrossFork(packageEntry);
+            }
 
             // Once we have the classloader, look up the WebViewFactoryProvider implementation and
             // call preloadInZygote() on it to give it the opportunity to preload the native library
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index e2485e9..a9bec41 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -177,7 +177,7 @@
 
             if (parsedArgs.preloadPackage != null) {
                 return handlePreloadPackage(parsedArgs.preloadPackage,
-                        parsedArgs.preloadPackageLibs);
+                        parsedArgs.preloadPackageLibs, parsedArgs.preloadPackageCacheKey);
             }
 
             if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
@@ -314,7 +314,7 @@
         return ZygoteInit.isPreloadComplete();
     }
 
-    protected boolean handlePreloadPackage(String packagePath, String libsPath) {
+    protected boolean handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
         throw new RuntimeException("Zyogte does not support package preloading");
     }
 
@@ -428,6 +428,7 @@
          */
         String preloadPackage;
         String preloadPackageLibs;
+        String preloadPackageCacheKey;
 
         /**
          * Whether this is a request to start preloading the default resources and classes.
@@ -599,6 +600,7 @@
                 } else if (arg.equals("--preload-package")) {
                     preloadPackage = args[++curArg];
                     preloadPackageLibs = args[++curArg];
+                    preloadPackageCacheKey = args[++curArg];
                 } else if (arg.equals("--preload-default")) {
                     preloadDefault = true;
                 } else {
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/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 713287e..8a7d1cf 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -135,6 +135,7 @@
         case PublicFormat::DEPTH16:
             return HAL_PIXEL_FORMAT_Y16;
         case PublicFormat::RAW_SENSOR:
+        case PublicFormat::RAW_DEPTH:
             return HAL_PIXEL_FORMAT_RAW16;
         default:
             // Most formats map 1:1
@@ -149,6 +150,7 @@
             return HAL_DATASPACE_V0_JFIF;
         case PublicFormat::DEPTH_POINT_CLOUD:
         case PublicFormat::DEPTH16:
+        case PublicFormat::RAW_DEPTH:
             return HAL_DATASPACE_DEPTH;
         case PublicFormat::RAW_SENSOR:
         case PublicFormat::RAW_PRIVATE:
@@ -182,8 +184,12 @@
             // Enums overlap in both name and value
             return static_cast<PublicFormat>(format);
         case HAL_PIXEL_FORMAT_RAW16:
-            // Name differs, though value is the same
-            return PublicFormat::RAW_SENSOR;
+            switch (dataSpace) {
+                case HAL_DATASPACE_DEPTH:
+                  return PublicFormat::RAW_DEPTH;
+                default:
+                  return PublicFormat::RAW_SENSOR;
+            }
         case HAL_PIXEL_FORMAT_RAW_OPAQUE:
             // Name differs, though value is the same
             return PublicFormat::RAW_PRIVATE;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 6fbf49b..066ce68 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -167,7 +167,7 @@
             buffer->getHeight(),
             buffer->getPixelFormat(),
             buffer->getUsage(),
-            (void*)buffer.get());
+            (jlong)buffer.get());
 }
 
 static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz,
diff --git a/core/jni/com_android_internal_os_FuseAppLoop.cpp b/core/jni/com_android_internal_os_FuseAppLoop.cpp
index dd003eb..e125150 100644
--- a/core/jni/com_android_internal_os_FuseAppLoop.cpp
+++ b/core/jni/com_android_internal_os_FuseAppLoop.cpp
@@ -20,140 +20,214 @@
 #include <stdlib.h>
 #include <sys/stat.h>
 
+#include <map>
+#include <memory>
+
 #include <android_runtime/Log.h>
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <jni.h>
 #include <libappfuse/FuseAppLoop.h>
 #include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 #include "core_jni_helpers.h"
 
 namespace android {
-
 namespace {
-
 constexpr const char* CLASS_NAME = "com/android/internal/os/FuseAppLoop";
 
 jclass gFuseAppLoopClass;
-jmethodID gOnGetSizeMethod;
+jmethodID gOnCommandMethod;
 jmethodID gOnOpenMethod;
-jmethodID gOnFsyncMethod;
-jmethodID gOnReleaseMethod;
-jmethodID gOnReadMethod;
-jmethodID gOnWriteMethod;
 
 class Callback : public fuse::FuseAppLoopCallback {
 private:
-    static constexpr size_t kBufferSize = std::max(fuse::kFuseMaxWrite, fuse::kFuseMaxRead);
-    static_assert(kBufferSize <= INT32_MAX, "kBufferSize should be fit in int32_t.");
-
+    typedef ScopedLocalRef<jbyteArray> LocalBytes;
     JNIEnv* const mEnv;
     jobject const mSelf;
-    ScopedLocalRef<jbyteArray> mJniBuffer;
-
-    template <typename T>
-    T checkException(T result) const {
-        if (mEnv->ExceptionCheck()) {
-            LOGE_EX(mEnv, nullptr);
-            mEnv->ExceptionClear();
-            return -EIO;
-        }
-        return result;
-    }
+    std::map<uint64_t, std::unique_ptr<LocalBytes>> mBuffers;
 
 public:
     Callback(JNIEnv* env, jobject self) :
-        mEnv(env),
-        mSelf(self),
-        mJniBuffer(env, nullptr) {}
+        mEnv(env), mSelf(self) {}
 
-    bool Init() {
-        mJniBuffer.reset(mEnv->NewByteArray(kBufferSize));
-        return mJniBuffer.get();
+    void OnLookup(uint64_t unique, uint64_t inode) override {
+        mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_LOOKUP, unique, inode, 0, 0, nullptr);
+        CHECK(!mEnv->ExceptionCheck());
     }
 
-    bool IsActive() override {
-        return true;
+    void OnGetAttr(uint64_t unique, uint64_t inode) override {
+        mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_GETATTR, unique, inode, 0, 0, nullptr);
+        CHECK(!mEnv->ExceptionCheck());
     }
 
-    int64_t OnGetSize(uint64_t inode) override {
-        return checkException(mEnv->CallLongMethod(mSelf, gOnGetSizeMethod, inode));
-    }
-
-    int32_t OnOpen(uint64_t inode) override {
-        return checkException(mEnv->CallIntMethod(mSelf, gOnOpenMethod, inode));
-    }
-
-    int32_t OnFsync(uint64_t inode) override {
-        return checkException(mEnv->CallIntMethod(mSelf, gOnFsyncMethod, inode));
-    }
-
-    int32_t OnRelease(uint64_t inode) override {
-        return checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode));
-    }
-
-    int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override {
-        CHECK_LE(size, static_cast<uint32_t>(kBufferSize));
-        const int32_t result = checkException(mEnv->CallIntMethod(
-                mSelf, gOnReadMethod, inode, offset, size, mJniBuffer.get()));
-        if (result <= 0) {
-            return result;
-        }
-        if (result > static_cast<int32_t>(size)) {
-            LOG(ERROR) << "Returned size is too large.";
-            return -EIO;
+    void OnOpen(uint64_t unique, uint64_t inode) override {
+        const jbyteArray buffer = static_cast<jbyteArray>(mEnv->CallObjectMethod(
+                mSelf, gOnOpenMethod, unique, inode));
+        CHECK(!mEnv->ExceptionCheck());
+        if (buffer == nullptr) {
+            return;
         }
 
-        mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer));
-        CHECK(!mEnv->ExceptionCheck());
-
-        return checkException(result);
+        mBuffers.insert(std::make_pair(inode, std::unique_ptr<LocalBytes>(
+                new LocalBytes(mEnv, buffer))));
     }
 
-    int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override {
-        CHECK_LE(size, static_cast<uint32_t>(kBufferSize));
+    void OnFsync(uint64_t unique, uint64_t inode) override {
+        mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_FSYNC, unique, inode, 0, 0, nullptr);
+        CHECK(!mEnv->ExceptionCheck());
+    }
 
-        mEnv->SetByteArrayRegion(mJniBuffer.get(), 0, size, static_cast<const jbyte*>(buffer));
+    void OnRelease(uint64_t unique, uint64_t inode) override {
+        mBuffers.erase(inode);
+        mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_RELEASE, unique, inode, 0, 0, nullptr);
+        CHECK(!mEnv->ExceptionCheck());
+    }
+
+    void OnRead(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size) override {
+        CHECK_LE(size, static_cast<uint32_t>(fuse::kFuseMaxRead));
+
+        auto it = mBuffers.find(inode);
+        CHECK(it != mBuffers.end());
+
+        mEnv->CallVoidMethod(
+                mSelf, gOnCommandMethod, FUSE_READ, unique, inode, offset, size,
+                it->second->get());
+        CHECK(!mEnv->ExceptionCheck());
+    }
+
+    void OnWrite(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size,
+            const void* buffer) override {
+        CHECK_LE(size, static_cast<uint32_t>(fuse::kFuseMaxWrite));
+
+        auto it = mBuffers.find(inode);
+        CHECK(it != mBuffers.end());
+
+        jbyteArray const javaBuffer = it->second->get();
+
+        mEnv->SetByteArrayRegion(javaBuffer, 0, size, static_cast<const jbyte*>(buffer));
         CHECK(!mEnv->ExceptionCheck());
 
-        return checkException(mEnv->CallIntMethod(
-                mSelf, gOnWriteMethod, inode, offset, size, mJniBuffer.get()));
+        mEnv->CallVoidMethod(
+                mSelf, gOnCommandMethod, FUSE_WRITE, unique, inode, offset, size, javaBuffer);
+        CHECK(!mEnv->ExceptionCheck());
     }
 };
 
-jboolean com_android_internal_os_FuseAppLoop_start_loop(JNIEnv* env, jobject self, jint jfd) {
-    base::unique_fd fd(jfd);
+jlong com_android_internal_os_FuseAppLoop_new(JNIEnv* env, jobject self, jint jfd) {
+    return reinterpret_cast<jlong>(new fuse::FuseAppLoop(base::unique_fd(jfd)));
+}
+
+void com_android_internal_os_FuseAppLoop_delete(JNIEnv* env, jobject self, jlong ptr) {
+    delete reinterpret_cast<fuse::FuseAppLoop*>(ptr);
+}
+
+void com_android_internal_os_FuseAppLoop_start(JNIEnv* env, jobject self, jlong ptr) {
     Callback callback(env, self);
+    reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Start(&callback);
+}
 
-    if (!callback.Init()) {
-        LOG(ERROR) << "Failed to init callback";
-        return JNI_FALSE;
+void com_android_internal_os_FuseAppLoop_replySimple(
+        JNIEnv* env, jobject self, jlong ptr, jlong unique, jint result) {
+    if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplySimple(unique, result)) {
+        reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
     }
+}
 
-    return fuse::StartFuseAppLoop(fd.release(), &callback);
+void com_android_internal_os_FuseAppLoop_replyOpen(
+        JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong fh) {
+    if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyOpen(unique, fh)) {
+        reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+    }
+}
+
+void com_android_internal_os_FuseAppLoop_replyLookup(
+        JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong inode, jint size) {
+    if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyLookup(unique, inode, size)) {
+        reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+    }
+}
+
+void com_android_internal_os_FuseAppLoop_replyGetAttr(
+        JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong inode, jint size) {
+    if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyGetAttr(
+            unique, inode, size, S_IFREG | 0777)) {
+        reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+    }
+}
+
+void com_android_internal_os_FuseAppLoop_replyWrite(
+        JNIEnv* env, jobject self, jlong ptr, jlong unique, jint size) {
+    if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyWrite(unique, size)) {
+        reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+    }
+}
+
+void com_android_internal_os_FuseAppLoop_replyRead(
+        JNIEnv* env, jobject self, jlong ptr, jlong unique, jint size, jbyteArray data) {
+    ScopedByteArrayRO array(env, data);
+    CHECK(size >= 0);
+    CHECK(static_cast<size_t>(size) < array.size());
+    if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyRead(unique, size, array.get())) {
+        reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+    }
 }
 
 const JNINativeMethod methods[] = {
     {
-        "native_start_loop",
-        "(I)Z",
-        (void *) com_android_internal_os_FuseAppLoop_start_loop
-    }
+        "native_new",
+        "(I)J",
+        reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_new)
+    },
+    {
+        "native_delete",
+        "(J)V",
+        reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_delete)
+    },
+    {
+        "native_start",
+        "(J)V",
+        reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_start)
+    },
+    {
+        "native_replySimple",
+        "(JJI)V",
+        reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replySimple)
+    },
+    {
+        "native_replyOpen",
+        "(JJJ)V",
+        reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyOpen)
+    },
+    {
+        "native_replyLookup",
+        "(JJJJ)V",
+        reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyLookup)
+    },
+    {
+        "native_replyGetAttr",
+        "(JJJJ)V",
+        reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyGetAttr)
+    },
+    {
+        "native_replyRead",
+        "(JJI[B)V",
+        reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyRead)
+    },
+    {
+        "native_replyWrite",
+        "(JJI)V",
+        reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyWrite)
+    },
 };
-
 }  // namespace
 
 int register_com_android_internal_os_FuseAppLoop(JNIEnv* env) {
     gFuseAppLoopClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
-    gOnGetSizeMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onGetSize", "(J)J");
-    gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(J)I");
-    gOnFsyncMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onFsync", "(J)I");
-    gOnReleaseMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRelease", "(J)I");
-    gOnReadMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRead", "(JJI[B)I");
-    gOnWriteMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onWrite", "(JJI[B)I");
+    gOnCommandMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onCommand", "(IJJJI[B)V");
+    gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(JJ)[B");
     RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods));
     return 0;
 }
-
 }  // namespace android
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 3f1bdff..d27c5a3 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -53,6 +53,7 @@
     RGBA_1010102      = 0x2b,
     JPEG              = 0x100,
     DEPTH_POINT_CLOUD = 0x101,
+    RAW_DEPTH         = 0x1002, // @hide
     YV12              = 0x32315659,
     Y8                = 0x20203859, // @hide
     Y16               = 0x20363159, // @hide
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8721f34..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" />
@@ -2312,6 +2316,10 @@
     <permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA"
                 android:protectionLevel="signature" />
 
+    <!-- @hide Allows an application to change the accessibility volume. -->
+    <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME"
+                android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to collect frame statistics -->
     <permission android:name="android.permission.FRAME_STATS"
          android:protectionLevel="signature" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 68e766e..db234e7 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.
@@ -2799,4 +2801,9 @@
 
     <!-- Colon separated list of package names that should be granted Notification Listener access -->
     <string name="config_defaultListenerAccessPackages" translatable="false"></string>
+
+    <!-- Maximum size, specified in pixels, to restrain the display space width to. Height and
+         density will be scaled accordingly to maintain aspect ratio. A value of 0 indicates no
+         constraint will be enforced. -->
+    <integer name="config_maxUiWidth">0</integer>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 868e256..4afa8dc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -189,22 +189,23 @@
     <!-- Displayed to tell the user that they cannot change the caller ID setting. -->
     <string name="CLIRPermanent">You can\'t change the caller ID setting.</string>
 
-    <!-- Displayed to tell the user that data service is blocked by access control. -->
-    <string name="RestrictedOnData">Data service is blocked.</string>
-    <!-- Displayed to tell the user that emergency service is blocked by access control. -->
-    <string name="RestrictedOnEmergency">Emergency service is blocked.</string>
-    <!-- Displayed to tell the user that normal service is blocked by access control. -->
-    <string name="RestrictedOnNormal">Voice service is blocked.</string>
-    <!-- Displayed to tell the user that all emergency and normal voice services are blocked by access control. -->
-    <string name="RestrictedOnAllVoice">All voice services are blocked.</string>
-    <!-- Displayed to tell the user that sms service is blocked by access control. -->
-    <string name="RestrictedOnSms">SMS service is blocked.</string>
-    <!-- Displayed to tell the user that voice/data service is blocked by access control. -->
-    <string name="RestrictedOnVoiceData">Voice/data services are blocked.</string>
-    <!-- Displayed to tell the user that voice and sms service are blocked by access control. -->
-    <string name="RestrictedOnVoiceSms">Voice/SMS services are blocked.</string>
-    <!-- Displayed to tell the user that all service is blocked by access control. -->
-    <string name="RestrictedOnAll">All voice/data/SMS services are blocked.</string>
+    <!-- Notification title to tell the user that data service is blocked by access control. -->
+    <string name="RestrictedOnDataTitle">No data service</string>
+    <!-- Notification title to tell the user that emergency service is blocked by access control. -->
+    <string name="RestrictedOnEmergencyTitle">No emergency service</string>
+    <!-- Notification title to tell the user that normal service is blocked by access control. -->
+    <string name="RestrictedOnNormalTitle">No voice service</string>
+    <!-- Notification title to tell the user that all emergency and normal voice services are blocked by access control. -->
+    <string name="RestrictedOnAllVoiceTitle">No voice/emergency service</string>
+
+    <!-- Notification content to tell the user that data service is blocked by access control. -->
+    <string name="RestrictedOnDataContent">Your carrier has temporarily suspended data service at this location</string>
+    <!-- Notification content to tell the user that emergency service is blocked by access control. -->
+    <string name="RestrictedOnEmergencyContent">Your carrier has temporarily suspended emergency calls at this location</string>
+    <!-- Notification content to tell the user that normal service is blocked by access control. -->
+    <string name="RestrictedOnNormalContent">Your carrier has temporarily suspended voice calls at this location</string>
+    <!-- Notification content to tell the user that all emergency and normal voice services are blocked by access control. -->
+    <string name="RestrictedOnAllVoiceContent">Your carrier has temporarily suspended voice and emergency calls at this location</string>
 
     <!-- Displayed to tell the user that they should switch their network preference. -->
     <string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b23c96c..92436f4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -507,12 +507,16 @@
   <java-symbol type="string" name="Noon" />
   <java-symbol type="string" name="PinMmi" />
   <java-symbol type="string" name="PwdMmi" />
-  <java-symbol type="string" name="RestrictedOnAllVoice" />
-  <java-symbol type="string" name="RestrictedOnData" />
-  <java-symbol type="string" name="RestrictedOnEmergency" />
-  <java-symbol type="string" name="RestrictedOnNormal" />
   <java-symbol type="string" name="NetworkPreferenceSwitchSummary" />
   <java-symbol type="string" name="NetworkPreferenceSwitchTitle" />
+  <java-symbol type="string" name="RestrictedOnAllVoiceTitle" />
+  <java-symbol type="string" name="RestrictedOnDataTitle" />
+  <java-symbol type="string" name="RestrictedOnEmergencyTitle" />
+  <java-symbol type="string" name="RestrictedOnNormalTitle" />
+  <java-symbol type="string" name="RestrictedOnAllVoiceContent" />
+  <java-symbol type="string" name="RestrictedOnDataContent" />
+  <java-symbol type="string" name="RestrictedOnEmergencyContent" />
+  <java-symbol type="string" name="RestrictedOnNormalContent" />
   <java-symbol type="string" name="SetupCallDefault" />
   <java-symbol type="string" name="accept" />
   <java-symbol type="string" name="activity_chooser_view_see_all" />
@@ -2919,6 +2923,9 @@
   <!-- Colon separated list of package names that should be granted Notification Listener access -->
   <java-symbol type="string" name="config_defaultListenerAccessPackages" />
 
+  <!-- maximum width of the display -->
+  <java-symbol type="integer" name="config_maxUiWidth" />
+
   <!-- system notification channels -->
   <java-symbol type="string" name="notification_channel_virtual_keyboard" />
   <java-symbol type="string" name="notification_channel_physical_keyboard" />
diff --git a/core/tests/coretests/README b/core/tests/coretests/README
index 4a69843..aced441 100644
--- a/core/tests/coretests/README
+++ b/core/tests/coretests/README
@@ -45,6 +45,10 @@
 
     -e debug true
 
+To uninstall the package:
+
+  adb shell pm uninstall -k com.android.frameworks.coretests
+
 For more arguments, see the guide to command=line testing:
 
   https://developer.android.com/studio/test/command-line.html
diff --git a/core/tests/coretests/src/android/content/ContentTests.java b/core/tests/coretests/src/android/content/ContentTests.java
index a1299e3..567b79a 100644
--- a/core/tests/coretests/src/android/content/ContentTests.java
+++ b/core/tests/coretests/src/android/content/ContentTests.java
@@ -23,6 +23,7 @@
         TestSuite suite = new TestSuite(ContentTests.class.getName());
 
         suite.addTestSuite(AssetTest.class);
+        suite.addTestSuite(ContentValuesTest.class);
         return suite;
     }
 }
diff --git a/core/tests/coretests/src/android/content/ContentValuesTest.java b/core/tests/coretests/src/android/content/ContentValuesTest.java
new file mode 100644
index 0000000..7b39939
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ContentValuesTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+
+/*
+  runtest -c android.content.ContentValuesTest frameworks-core
+
+  or
+
+  make -j256 FrameworksCoreTests && \
+    adb shell pm uninstall -k com.android.frameworks.coretests && \
+    adb install out/target/product/bullhead/testcases/FrameworksCoreTests/FrameworksCoreTests.apk && \
+    adb shell am instrument -w -e package android.content \
+      com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+*/
+public class ContentValuesTest extends AndroidTestCase {
+
+    @SmallTest
+    public void testIsEmpty() throws Exception {
+        ContentValues cv = new ContentValues();
+        assertTrue(cv.isEmpty());
+        assertEquals(0, cv.size());
+
+        cv.put("key", "value");
+        assertFalse(cv.isEmpty());
+        assertEquals(1, cv.size());
+    }
+}
diff --git a/core/tests/coretests/src/android/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/os/storage/StorageManagerIntegrationTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
index ff98eb7..fbba6ff 100644
--- a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
@@ -264,7 +264,7 @@
         final MyThreadFactory factory = new MyThreadFactory();
         int firstMountId;
         try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
-                ParcelFileDescriptor.MODE_READ_ONLY, callback, factory)) {
+                ParcelFileDescriptor.MODE_READ_ONLY, callback, null, factory)) {
             assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
             firstMountId = mSm.getProxyFileDescriptorMountPointId();
             assertNotSame(-1, firstMountId);
@@ -276,7 +276,7 @@
 
         // StorageManager should mount another bridge on the next open request.
         try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
-                ParcelFileDescriptor.MODE_WRITE_ONLY, callback, factory)) {
+                ParcelFileDescriptor.MODE_WRITE_ONLY, callback, null, factory)) {
             assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
             assertNotSame(firstMountId, mSm.getProxyFileDescriptorMountPointId());
         }
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/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index 33d19d4..d69f67d 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -22,6 +22,7 @@
 import android.annotation.HalfFloat;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.Size;
 
 import android.util.Half;
@@ -288,7 +289,7 @@
  * and <code>(1.0, 0.0, 0.0, 0.5)</code>.</p>
  */
 @AnyThread
-public class Color {
+public final class Color {
     @ColorInt public static final int BLACK       = 0xFF000000;
     @ColorInt public static final int DKGRAY      = 0xFF444444;
     @ColorInt public static final int GRAY        = 0xFF888888;
@@ -415,7 +416,7 @@
      * to this color space's color model, plus one extra component for
      * alpha.
      *
-     * @return An integer between 4 and 5
+     * @return The integer 4 or 5
      */
     @IntRange(from = 4, to = 5)
     public int getComponentCount() {
@@ -560,7 +561,37 @@
     @NonNull
     @Size(min = 4, max = 5)
     public float[] getComponents() {
-        return Arrays.copyOf(mComponents, mColorSpace.getComponentCount() + 1);
+        return Arrays.copyOf(mComponents, mComponents.length);
+    }
+
+    /**
+     * Copies this color's components in the supplied array. The last element of the
+     * array is always the alpha component.
+     *
+     * @param components An array of floats whose size must be at least
+     *                  {@link #getComponentCount()}, can be null
+     * @return The array passed as a parameter if not null, or a new array of length
+     *         {@link #getComponentCount()}
+     *
+     * @see #getComponent(int)
+     *
+     * @throws IllegalArgumentException If the specified array's length is less than
+     * {@link #getComponentCount()}
+     */
+    @NonNull
+    @Size(min = 4)
+    public float[] getComponents(@Nullable @Size(min = 4) float[] components) {
+        if (components == null) {
+            return Arrays.copyOf(mComponents, mComponents.length);
+        }
+
+        if (components.length < mComponents.length) {
+            throw new IllegalArgumentException("The specified array's length must be at "
+                    + "least " + mComponents.length);
+        }
+
+        System.arraycopy(mComponents, 0, components, 0, mComponents.length);
+        return components;
     }
 
     /**
@@ -570,7 +601,7 @@
      *
      * <p>If the requested component index is {@link #getComponentCount()},
      * this method returns the alpha component, always in the range
-     * \([0..1\).</p>
+     * \([0..1]\).</p>
      *
      * @see #getComponents()
      *
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index b78df34..ff9f11d 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -189,9 +189,8 @@
                 skip(parser);
             }
         }
-        String fullFilename = "/system/fonts/" +
-                FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
-        return new FontConfig.Font(fullFilename, index,
+        String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
+        return new FontConfig.Font(sanitizedName, index,
                 axes.toArray(new FontConfig.Axis[axes.size()]), weight, isItalic);
     }
 
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index a226e85..e3527e3 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -662,6 +662,15 @@
     public static final int DEPTH_POINT_CLOUD = 0x101;
 
     /**
+     * Unprocessed implementation-dependent raw
+     * depth measurements, opaque with 16 bit
+     * samples.
+     *
+     * @hide
+     */
+    public static final int RAW_DEPTH = 0x1002;
+
+    /**
      * Android private opaque image format.
      * <p>
      * The choices of the actual format and pixel data layout are entirely up to
@@ -723,6 +732,7 @@
                 return 24;
             case FLEX_RGBA_8888:
                 return 32;
+            case RAW_DEPTH:
             case RAW_SENSOR:
                 return 16;
             case RAW10:
@@ -765,6 +775,7 @@
             case DEPTH16:
             case DEPTH_POINT_CLOUD:
             case PRIVATE:
+            case RAW_DEPTH:
                 return true;
         }
 
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 228d950..8c3a2e8 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1003,21 +1003,22 @@
             Map<String, ByteBuffer> bufferForPath) {
         FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
         for (FontConfig.Font font : family.getFonts()) {
-            ByteBuffer fontBuffer = bufferForPath.get(font.getFontName());
+            String fullPathName = "/system/fonts/" + font.getFontName();
+            ByteBuffer fontBuffer = bufferForPath.get(fullPathName);
             if (fontBuffer == null) {
-                try (FileInputStream file = new FileInputStream(font.getFontName())) {
+                try (FileInputStream file = new FileInputStream(fullPathName)) {
                     FileChannel fileChannel = file.getChannel();
                     long fontSize = fileChannel.size();
                     fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
-                    bufferForPath.put(font.getFontName(), fontBuffer);
+                    bufferForPath.put(fullPathName, fontBuffer);
                 } catch (IOException e) {
-                    Log.e(TAG, "Error mapping font file " + font.getFontName());
+                    Log.e(TAG, "Error mapping font file " + fullPathName);
                     continue;
                 }
             }
             if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(),
                     font.getWeight(), font.isItalic() ? Builder.ITALIC : Builder.NORMAL)) {
-                Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
+                Log.e(TAG, "Error creating font " + fullPathName + "#" + font.getTtcIndex());
             }
         }
         fontFamily.freeze();
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index e8972aa..d154730 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -363,6 +363,7 @@
     state.computedState.transform.copyTo(&info.transform[0]);
 
     mRenderState.invokeFunctor(op.functor, DrawGlInfo::kModeDraw, &info);
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
 }
 
 void BakedOpRenderer::dirtyRenderTarget(const Rect& uiDirty) {
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 7be71ee..8126d57 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -264,10 +264,15 @@
             // the actual time spent blocked.
             nsecs_t forgiveAmount = std::min(expectedDequeueDuration,
                     frame[FrameInfoIndex::DequeueBufferDuration]);
+            LOG_ALWAYS_FATAL_IF(forgiveAmount >= totalDuration,
+                    "Impossible dequeue duration! dequeue duration reported %" PRId64
+                    ", total duration %" PRId64, forgiveAmount, totalDuration);
             totalDuration -= forgiveAmount;
         }
     }
+    LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
     uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
+    LOG_ALWAYS_FATAL_IF(framebucket < 0, "framebucket < 0 (%u)", framebucket);
     // Keep the fast path as fast as possible.
     if (CC_LIKELY(totalDuration < mFrameInterval)) {
         mData->frameCounts[framebucket]++;
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 8823a92..f6b2912 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -491,6 +491,36 @@
     return *mCache.bitmap;
 }
 
+void Tree::updateCache(sk_sp<SkSurface> surface) {
+    if (surface.get()) {
+        mCache.surface = surface;
+    }
+    if (surface.get() || mCache.dirty) {
+        SkSurface* vdSurface = mCache.surface.get();
+        SkCanvas* canvas = vdSurface->getCanvas();
+        float scaleX = vdSurface->width() / mProperties.getViewportWidth();
+        float scaleY = vdSurface->height() / mProperties.getViewportHeight();
+        SkAutoCanvasRestore acr(canvas, true);
+        canvas->clear(SK_ColorTRANSPARENT);
+        canvas->scale(scaleX, scaleY);
+        mRootNode->draw(canvas, false);
+        mCache.dirty = false;
+        canvas->flush();
+    }
+}
+
+void Tree::draw(SkCanvas* canvas) {
+   /*
+    * TODO address the following...
+    *
+    * 1) figure out how to set path's as volatile during animation
+    * 2) if mRoot->getPaint() != null either promote to layer (during
+    *    animation) or cache in SkSurface (for static content)
+    */
+    canvas->drawImageRect(mCache.surface->makeImageSnapshot().get(),
+        mutateProperties()->getBounds(), getPaint());
+}
+
 void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {
     SkBitmap outCache;
     bitmap.getSkBitmap(&outCache);
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 729a4dd..22cfe29 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -31,6 +31,7 @@
 #include <SkPathMeasure.h>
 #include <SkRect.h>
 #include <SkShader.h>
+#include <SkSurface.h>
 
 #include <cutils/compiler.h>
 #include <stddef.h>
@@ -677,15 +678,37 @@
     // This should only be called from animations on RT
     TreeProperties* mutateProperties() { return &mProperties; }
 
+    // called from RT only
+    const TreeProperties& properties() const { return mProperties; }
+
     // This should always be called from RT.
     void markDirty() { mCache.dirty = true; }
     bool isDirty() const { return mCache.dirty; }
     bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; }
     void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; }
 
+    // Returns true if VD cache surface is big enough. This should always be called from RT and it
+    // works with Skia pipelines only.
+    bool canReuseSurface() {
+        SkSurface* surface = mCache.surface.get();
+        return surface && surface->width() >= mProperties.getScaledWidth()
+              && surface->height() >= mProperties.getScaledHeight();
+    }
+
+    // Draws VD cache into a canvas. This should always be called from RT and it works with Skia
+    // pipelines only.
+    void draw(SkCanvas* canvas);
+
+    // Draws VD into a GPU backed surface. If canReuseSurface returns false, then "surface" must
+    // contain a new surface. This should always be called from RT and it works with Skia pipelines
+    // only.
+    void updateCache(sk_sp<SkSurface> surface);
+
 private:
     struct Cache {
-        sk_sp<Bitmap> bitmap;
+        sk_sp<Bitmap> bitmap; //used by HWUI pipeline and software
+        //TODO: use surface instead of bitmap when drawing in software canvas
+        sk_sp<SkSurface> surface; //used only by Skia pipelines
         bool dirty = true;
     };
 
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 496f7ba..3ddc09f 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -19,6 +19,7 @@
 #include "renderthread/CanvasContext.h"
 #include "VectorDrawable.h"
 #include "DumpOpsCanvas.h"
+#include "SkiaPipeline.h"
 
 #include <SkImagePriv.h>
 
@@ -92,6 +93,8 @@
         // If any vector drawable in the display list needs update, damage the node.
         if (vectorDrawable->isDirty()) {
             isDirty = true;
+            static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline())
+                ->getVectorDrawables()->push_back(vectorDrawable);
         }
         vectorDrawable->setPropertyChangeWillBeConsumed(true);
     }
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 10c1865..75f1adc 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -25,6 +25,7 @@
 #include <SkPictureRecorder.h>
 #include <SkPixelSerializer.h>
 #include <SkStream.h>
+#include "VectorDrawable.h"
 
 #include <unistd.h>
 
@@ -40,7 +41,9 @@
 
 Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN};
 
-SkiaPipeline::SkiaPipeline(RenderThread& thread) :  mRenderThread(thread) { }
+SkiaPipeline::SkiaPipeline(RenderThread& thread) :  mRenderThread(thread) {
+    mVectorDrawables.reserve(30);
+}
 
 TaskManager* SkiaPipeline::getTaskManager() {
     return &mTaskManager;
@@ -74,6 +77,7 @@
         const BakedOpRenderer::LightInfo& lightInfo) {
     updateLighting(lightGeometry, lightInfo);
     ATRACE_NAME("draw layers");
+    renderVectorDrawableCache();
     renderLayersImpl(*layerUpdateQueue, opaque);
     layerUpdateQueue->clear();
 }
@@ -176,10 +180,35 @@
     }
 };
 
+void SkiaPipeline::renderVectorDrawableCache() {
+    //render VectorDrawables into offscreen buffers
+    for (auto vd : mVectorDrawables) {
+        sk_sp<SkSurface> surface;
+        if (!vd->canReuseSurface()) {
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
+            sk_sp<SkColorSpace> colorSpace = nullptr;
+#else
+            sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
+#endif
+            int scaledWidth = SkScalarCeilToInt(vd->properties().getScaledWidth());
+            int scaledHeight = SkScalarCeilToInt(vd->properties().getScaledHeight());
+            SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight,
+                    kPremul_SkAlphaType, colorSpace);
+            SkASSERT(mRenderThread.getGrContext() != nullptr);
+            surface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
+                    info);
+        }
+        vd->updateCache(surface);
+    }
+    mVectorDrawables.clear();
+}
+
 void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
         const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds,
         sk_sp<SkSurface> surface) {
 
+    renderVectorDrawableCache();
+
     // draw all layers up front
     renderLayersImpl(layers, opaque);
 
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index c58fedf..6f5e719 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -49,6 +49,8 @@
             const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds,
             sk_sp<SkSurface> surface);
 
+    std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
+
     static void destroyLayer(RenderNode* node);
 
     static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
@@ -119,8 +121,18 @@
             const std::vector< sp<RenderNode> >& nodes, const Rect &contentDrawBounds,
             sk_sp<SkSurface>);
 
+    /**
+     *  Render mVectorDrawables into offscreen buffers.
+     */
+    void renderVectorDrawableCache();
+
     TaskManager mTaskManager;
     std::vector<sk_sp<SkImage>> mPinnedImages;
+
+    /**
+     *  populated by prepareTree with dirty VDs
+     */
+    std::vector<VectorDrawableRoot*> mVectorDrawables;
     static float mLightRadius;
     static uint8_t mAmbientShadowAlpha;
     static uint8_t mSpotShadowAlpha;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 559d268..27edc25 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -126,22 +126,7 @@
          return SkRect::MakeLargest();
      }
      virtual void onDraw(SkCanvas* canvas) override {
-         Bitmap& hwuiBitmap = mRoot->getBitmapUpdateIfDirty();
-         SkBitmap bitmap;
-         hwuiBitmap.getSkBitmap(&bitmap);
-         SkPaint* paint = mRoot->getPaint();
-         canvas->drawBitmapRect(bitmap, mRoot->mutateProperties()->getBounds(), paint);
-         /*
-          * TODO we can draw this directly but need to address the following...
-          *
-          * 1) Add drawDirect(SkCanvas*) to VectorDrawableRoot
-          * 2) fix VectorDrawable.cpp's Path::draw to not make a temporary path
-          *    so that we don't break caching
-          * 3) figure out how to set path's as volatile during animation
-          * 4) if mRoot->getPaint() != null either promote to layer (during
-          *    animation) or cache in SkSurface (for static content)
-          *
-          */
+         mRoot->draw(canvas);
      }
 
  private:
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 738c091..33eda96 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -194,6 +194,8 @@
 
     void waitOnFences();
 
+    IRenderPipeline* getRenderPipeline() { return mRenderPipeline.get(); }
+
 private:
     CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
             IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
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/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 7ae58a6..e15f5d9 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -17,6 +17,7 @@
 #include "tests/common/TestUtils.h"
 
 #include <gtest/gtest.h>
+#include <SkBlurDrawLooper.h>
 #include <SkColorMatrixFilter.h>
 #include <SkColorSpace.h>
 #include <SkImagePriv.h>
@@ -95,3 +96,16 @@
     sk_sp<SkColorSpace> sRGB2 = SkColorSpace::MakeSRGB();
     ASSERT_EQ(sRGB1.get(), sRGB2.get());
 }
+
+TEST(SkiaBehavior, blurDrawLooper) {
+    sk_sp<SkDrawLooper> looper = SkBlurDrawLooper::Make(SK_ColorRED, 5.0f, 3.0f, 4.0f);
+
+    SkDrawLooper::BlurShadowRec blur;
+    bool success = looper->asABlurShadow(&blur);
+    ASSERT_TRUE(success);
+
+    ASSERT_EQ(SK_ColorRED, blur.fColor);
+    ASSERT_EQ(5.0f, blur.fSigma);
+    ASSERT_EQ(3.0f, blur.fOffset.fX);
+    ASSERT_EQ(4.0f, blur.fOffset.fY);
+}
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index b60dbd5..0a1de33 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -15,6 +15,7 @@
  */
 package android.media;
 
+import android.util.IntArray;
 import com.android.server.LocalServices;
 
 /**
@@ -43,6 +44,8 @@
 
     public abstract void updateRingerModeAffectedStreamsInternal();
 
+    public abstract void setAccessibilityServiceUids(IntArray uids);
+
     public interface RingerModeDelegate {
         /** Called when external ringer mode is evaluated, returns the new internal ringer mode */
         int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index abf6b20..2a0e04e 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -62,6 +62,7 @@
             case ImageFormat.RAW12:
             case ImageFormat.DEPTH16:
             case ImageFormat.DEPTH_POINT_CLOUD:
+            case ImageFormat.RAW_DEPTH:
                 return 1;
             case ImageFormat.PRIVATE:
                 return 0;
@@ -103,6 +104,10 @@
             throw new IllegalArgumentException(
                     "Copy of RAW_OPAQUE format has not been implemented");
         }
+        if (src.getFormat() == ImageFormat.RAW_DEPTH) {
+            throw new IllegalArgumentException(
+                    "Copy of RAW_DEPTH format has not been implemented");
+        }
         if (!(dst.getOwner() instanceof ImageWriter)) {
             throw new IllegalArgumentException("Destination image is not from ImageWriter. Only"
                     + " the images from ImageWriter are writable");
@@ -206,6 +211,7 @@
             case PixelFormat.RGB_565:
             case ImageFormat.YUY2:
             case ImageFormat.Y16:
+            case ImageFormat.RAW_DEPTH:
             case ImageFormat.RAW_SENSOR:
             case ImageFormat.RAW_PRIVATE: // round estimate, real size is unknown
             case ImageFormat.DEPTH16:
@@ -253,6 +259,7 @@
             case ImageFormat.RAW_SENSOR:
             case ImageFormat.RAW10:
             case ImageFormat.RAW12:
+            case ImageFormat.RAW_DEPTH:
                 return new Size(image.getWidth(), image.getHeight());
             case ImageFormat.PRIVATE:
                 return new Size(0, 0);
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/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/TvContract.java b/media/java/android/media/tv/TvContract.java
index 6808b57..7bf69c0 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1321,9 +1321,7 @@
          *
          * <p>Type: INTEGER (boolean)
          * @see Channels#COLUMN_TRANSIENT
-         * @hide
          */
-        @SystemApi
         public static final String COLUMN_TRANSIENT = "transient";
 
         /**
@@ -2164,10 +2162,9 @@
          * specified, this value is set to 0 (not transient) by default.
          *
          * <p>Type: INTEGER (boolean)
-         * @see Programs#COLUMN_TRANSIENT
-         * @hide
+         * @see PreviewPrograms#COLUMN_TRANSIENT
+         * @see WatchNextPrograms#COLUMN_TRANSIENT
          */
-        @SystemApi
         public static final String COLUMN_TRANSIENT = "transient";
 
         /**
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/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index e49463f..227d804 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -58,6 +58,7 @@
 import android.widget.ArrayAdapter;
 import android.widget.TextView;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.Preconditions;
 
@@ -109,6 +110,11 @@
     private final ScanCallback mBLEScanCallback = new ScanCallback() {
         @Override
         public void onScanResult(int callbackType, ScanResult result) {
+            if (DEBUG) {
+                Log.i(LOG_TAG,
+                        "BLE.onScanResult(callbackType = " + callbackType + ", result = " + result
+                                + ")");
+            }
             final DeviceFilterPair<ScanResult> deviceFilterPair
                     = DeviceFilterPair.findMatch(result, mBLEFilters);
             if (deviceFilterPair == null) return;
@@ -125,6 +131,10 @@
     private BroadcastReceiver mBluetoothDeviceFoundBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Log.i(LOG_TAG,
+                        "BL.onReceive(context = " + context + ", intent = " + intent + ")");
+            }
             final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
             final DeviceFilterPair<BluetoothDevice> deviceFilterPair
                     = DeviceFilterPair.findMatch(device, mBluetoothFilters);
@@ -180,15 +190,23 @@
     }
 
     private void startDiscovery(AssociationRequest request) {
-        mRequest = request;
+        if (!request.equals(mRequest)) {
+            mRequest = request;
 
-        mFilters = request.getDeviceFilters();
-        mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
-        mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
-        mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
-        mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
+            mFilters = request.getDeviceFilters();
+            mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
+            mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
+            mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
+            mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
 
-        reset();
+            reset();
+        } else if (DEBUG) Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request);
+
+
+
+        if (!ArrayUtils.isEmpty(mDevicesFound)) {
+            onReadyToShowUI();
+        }
 
         if (shouldScan(mBluetoothFilters)) {
             final IntentFilter intentFilter = new IntentFilter();
@@ -215,6 +233,7 @@
     }
 
     private void reset() {
+        if (DEBUG) Log.i(LOG_TAG, "reset()");
         mDevicesFound.clear();
         mSelectedDevice = null;
         mDevicesAdapter.notifyDataSetChanged();
@@ -228,10 +247,18 @@
 
     private void stopScan() {
         if (DEBUG) Log.i(LOG_TAG, "stopScan() called");
-        mBluetoothAdapter.cancelDiscovery();
-        mBLEScanner.stopScan(mBLEScanCallback);
-        unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
-        unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);
+
+        if (shouldScan(mBluetoothFilters)) {
+            mBluetoothAdapter.cancelDiscovery();
+            unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
+        }
+        if (shouldScan(mBLEFilters)) {
+            mBLEScanner.stopScan(mBLEScanCallback);
+        }
+        if (shouldScan(mWifiFilters)) {
+            unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);
+        }
+
         stopSelf();
     }
 
@@ -355,8 +382,15 @@
         public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
                 T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
             if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
-            final DeviceFilter<T> matchingFilter = CollectionUtils.find(filters, (f) -> f.matches(dev));
-            return matchingFilter != null ? new DeviceFilterPair<>(dev, matchingFilter) : null;
+            final DeviceFilter<T> matchingFilter
+                    = CollectionUtils.find(filters, f -> f.matches(dev));
+
+            DeviceFilterPair<T> result = matchingFilter != null
+                    ? new DeviceFilterPair<>(dev, matchingFilter)
+                    : null;
+            if (DEBUG) Log.i(LOG_TAG, "findMatch(dev = " + dev + ", filters = " + filters +
+                    ") -> " + result);
+            return result;
         }
 
         public String getDisplayName() {
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 0bf1fa4..28f6877 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -223,6 +223,7 @@
         <item>Optimized for Audio Quality (990kbps/909kbps)</item>
         <item>Balanced Audio And Connection Quality (660kbps/606kbps)</item>
         <item>Optimized for Connection Quality (330kbps/303kbps)</item>
+        <item>Best Effort (Adaptive Bit Rate)</item>
     </string-array>
 
     <!-- Values for Bluetooth Audio Codec LDAC Playback Quaility selection preference. -->
@@ -230,6 +231,7 @@
         <item>1000</item>
         <item>1001</item>
         <item>1002</item>
+        <item>1003</item>
     </string-array>
 
     <!-- Summaries for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70]-->
@@ -237,6 +239,7 @@
         <item>Optimized for Audio Quality</item>
         <item>Balanced Audio And Connection Quality</item>
         <item>Optimized for Connection Quality</item>
+        <item>Best Effort (Adaptive Bit Rate)</item>
     </string-array>
 
     <!-- Titles for logd limit size selection preference. [CHAR LIMIT=14] -->
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index cc2dc1b..f14d0d1 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -76,7 +76,7 @@
     <!-- Status message of Wi-Fi when it is automatically connected by a network recommendation provider. [CHAR LIMIT=NONE] -->
     <string name="connected_via_network_scorer">Automatically connected via %1$s</string>
     <!-- Status message of Wi-Fi when it is automatically connected by a default network recommendation provider. [CHAR LIMIT=NONE] -->
-    <string name="connected_via_network_scorer_default">Automatically connected via Network Quality Scorer</string>
+    <string name="connected_via_network_scorer_default">Automatically connected via network rating provider</string>
     <!-- Status message of Wi-Fi when it is connected by Passpoint configuration. [CHAR LIMIT=NONE] -->
     <string name="connected_via_passpoint">Connected via %1$s</string>
     <!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
index 4b0ab59..b54d7e20 100644
--- a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
+++ b/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
@@ -106,11 +106,11 @@
 
     public SuggestionParser(
         Context context, SharedPreferences sharedPrefs, int orderXml, String smartDismissControl) {
-        mContext = context;
-        mSuggestionList = (List<SuggestionCategory>) new SuggestionOrderInflater(mContext)
-                .parse(orderXml);
-        mSharedPrefs = sharedPrefs;
-        mSmartDismissControl = smartDismissControl;
+        this(
+                context,
+                sharedPrefs,
+                (List<SuggestionCategory>) new SuggestionOrderInflater(context).parse(orderXml),
+                smartDismissControl);
     }
 
     public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
@@ -118,12 +118,15 @@
     }
 
     @VisibleForTesting
-    public SuggestionParser(Context context, SharedPreferences sharedPrefs) {
+    public SuggestionParser(
+            Context context,
+            SharedPreferences sharedPrefs,
+            List<SuggestionCategory> suggestionList,
+            String smartDismissControl) {
         mContext = context;
-        mSuggestionList = new ArrayList<SuggestionCategory>();
+        mSuggestionList = suggestionList;
         mSharedPrefs = sharedPrefs;
-        mSmartDismissControl = DEFAULT_SMART_DISMISS_CONTROL;
-        Log.wtf(TAG, "Only use this constructor for testing");
+        mSmartDismissControl = smartDismissControl;
     }
 
     public List<Tile> getSuggestions() {
@@ -134,7 +137,19 @@
         List<Tile> suggestions = new ArrayList<>();
         final int N = mSuggestionList.size();
         for (int i = 0; i < N; i++) {
-            readSuggestions(mSuggestionList.get(i), suggestions, isSmartSuggestionEnabled);
+            final SuggestionCategory category = mSuggestionList.get(i);
+            if (category.exclusive) {
+                // If suggestions from an exclusive category are present, parsing is stopped
+                // and only suggestions from that category are displayed. Note that subsequent
+                // exclusive categories are also ignored.
+                List<Tile> exclusiveSuggestions = new ArrayList<>();
+                readSuggestions(category, exclusiveSuggestions, isSmartSuggestionEnabled);
+                if (!exclusiveSuggestions.isEmpty()) {
+                    return exclusiveSuggestions;
+                }
+            } else {
+                readSuggestions(category, suggestions, isSmartSuggestionEnabled);
+            }
         }
         return suggestions;
     }
@@ -368,6 +383,7 @@
         public String category;
         public String pkg;
         public boolean multiple;
+        public boolean exclusive;
     }
 
     private static class SuggestionOrderInflater {
@@ -377,6 +393,7 @@
         private static final String ATTR_CATEGORY = "category";
         private static final String ATTR_PACKAGE = "package";
         private static final String ATTR_MULTIPLE = "multiple";
+        private static final String ATTR_EXCLUSIVE = "exclusive";
 
         private final Context mContext;
 
@@ -451,6 +468,9 @@
                 category.pkg = attrs.getAttributeValue(null, ATTR_PACKAGE);
                 String multiple = attrs.getAttributeValue(null, ATTR_MULTIPLE);
                 category.multiple = !TextUtils.isEmpty(multiple) && Boolean.parseBoolean(multiple);
+                String exclusive = attrs.getAttributeValue(null, ATTR_EXCLUSIVE);
+                category.exclusive =
+                        !TextUtils.isEmpty(exclusive) && Boolean.parseBoolean(exclusive);
                 return category;
             } else {
                 throw new IllegalArgumentException("Unknown item " + name);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 55c886e..1f86f8b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -95,7 +95,7 @@
 
     private WifiTrackerNetworkCallback mNetworkCallback;
 
-    private boolean mSavedNetworksExist;
+    private int mNumSavedNetworks;
     private boolean mRegistered;
 
     /** Updated using main handler. Clone of this collection is returned
@@ -363,11 +363,11 @@
     }
 
     /**
-     * @return true when there are saved networks on the device, regardless
-     * of whether the WifiTracker is tracking saved networks.
+     * Returns the number of saved networks on the device, regardless of whether the WifiTracker
+     * is tracking saved networks.
      */
-    public boolean doSavedNetworksExist() {
-        return mSavedNetworksExist;
+    public int getNumSavedNetworks() {
+        return mNumSavedNetworks;
     }
 
     public boolean isConnected() {
@@ -461,11 +461,12 @@
 
         final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
         if (configs != null) {
-            mSavedNetworksExist = configs.size() != 0;
+            mNumSavedNetworks = 0;
             for (WifiConfiguration config : configs) {
                 if (config.selfAdded && config.numAssociation == 0) {
                     continue;
                 }
+                mNumSavedNetworks++;
                 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
                 if (mLastInfo != null && mLastNetworkInfo != null) {
                     accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
@@ -525,9 +526,13 @@
                         // the given ScanResult.  This is used for showing that a given AP
                         // (ScanResult) is available via a Passpoint provider (provider friendly
                         // name).
-                        WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
-                        if (config != null) {
-                            accessPoint.update(config);
+                        try {
+                            WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
+                            if (config != null) {
+                                accessPoint.update(config);
+                            }
+                        } catch (UnsupportedOperationException e) {
+                            // Passpoint not supported on the device.
                         }
                     }
 
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index e100884..46726f2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -395,6 +395,26 @@
     }
 
     @Test
+    public void testGetNumSavedNetworks() throws InterruptedException {
+        WifiConfiguration validConfig = new WifiConfiguration();
+        validConfig.SSID = SSID_1;
+        validConfig.BSSID = BSSID_1;
+
+        WifiConfiguration selfAddedNoAssociation = new WifiConfiguration();
+        selfAddedNoAssociation.selfAdded = true;
+        selfAddedNoAssociation.numAssociation = 0;
+        selfAddedNoAssociation.SSID = SSID_2;
+        selfAddedNoAssociation.BSSID = BSSID_2;
+
+        when(mockWifiManager.getConfiguredNetworks())
+                .thenReturn(Arrays.asList(validConfig, selfAddedNoAssociation));
+
+        WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
+
+        assertEquals(1, tracker.getNumSavedNetworks());
+    }
+
+    @Test
     public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException {
         WifiTracker tracker =  createTrackerWithScanResultsAndAccessPoint1Connected();
 
diff --git a/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml b/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
index 1eeafba..0e2ce3b 100644
--- a/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
+++ b/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
@@ -15,6 +15,8 @@
 -->
 
 <optional-steps>
+    <step category="com.android.settings.suggested.category.DEFERRED_SETUP"
+        exclusive="true" />
     <step category="com.android.settings.suggested.category.LOCK_SCREEN" />
     <step category="com.android.settings.suggested.category.EMAIL" />
     <step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java
index 7729dec..d534180 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java
@@ -16,11 +16,12 @@
 
 package com.android.settingslib;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
@@ -31,59 +32,59 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.res.ResourceLoader;
+import org.robolectric.res.builder.DefaultPackageManager;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
 @RunWith(SettingLibRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class SuggestionParserTest {
 
-    @Mock
-    private PackageManager mPackageManager;
     private Context mContext;
     private SuggestionParser mSuggestionParser;
-    private SuggestionParser.SuggestionCategory mSuggestioCategory;
+    private SuggestionParser.SuggestionCategory mMultipleCategory;
+    private SuggestionParser.SuggestionCategory mExclusiveCategory;
     private List<Tile> mSuggestionsBeforeDismiss;
     private List<Tile> mSuggestionsAfterDismiss;
     private SharedPreferences mPrefs;
     private Tile mSuggestion;
-    private List<ResolveInfo> mInfo;
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(RuntimeEnvironment.application);
-        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        RuntimeEnvironment.setRobolectricPackageManager(
+                new TestPackageManager(RuntimeEnvironment.getAppResourceLoader()));
+        mContext = RuntimeEnvironment.application;
         mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
         mSuggestion = new Tile();
         mSuggestion.intent = new Intent("action");
         mSuggestion.intent.setComponent(new ComponentName("pkg", "cls"));
         mSuggestion.metaData = new Bundle();
+        mMultipleCategory = new SuggestionParser.SuggestionCategory();
+        mMultipleCategory.category = "category1";
+        mMultipleCategory.multiple = true;
+        mExclusiveCategory = new SuggestionParser.SuggestionCategory();
+        mExclusiveCategory.category = "category2";
+        mExclusiveCategory.exclusive = true;
         mSuggestionParser = new SuggestionParser(
-            mContext, mPrefs, R.xml.suggestion_ordering, "0,0");
-        mSuggestioCategory = new SuggestionParser.SuggestionCategory();
-        mSuggestioCategory.category = "category1";
-        mSuggestioCategory.multiple = true;
-        mInfo = new ArrayList<>();
+                mContext, mPrefs, Arrays.asList(mMultipleCategory, mExclusiveCategory), "0,0");
+
         ResolveInfo info1 = TileUtilsTest.newInfo(true, "category1");
         info1.activityInfo.packageName = "pkg";
         ResolveInfo info2 = TileUtilsTest.newInfo(true, "category1");
         info2.activityInfo.packageName = "pkg2";
-        mInfo.add(info1);
-        mInfo.add(info2);
-        when(mPackageManager.queryIntentActivitiesAsUser(
-            any(Intent.class), anyInt(), anyInt())).thenReturn(mInfo);
+        ResolveInfo info3 = TileUtilsTest.newInfo(true, "category2");
+        info3.activityInfo.packageName = "pkg3";
+
+        Intent intent1 = new Intent(Intent.ACTION_MAIN).addCategory("category1");
+        Intent intent2 = new Intent(Intent.ACTION_MAIN).addCategory("category2");
+        RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(intent1, info1);
+        RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(intent1, info2);
+        RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(intent2, info3);
     }
 
     @Test
@@ -99,30 +100,64 @@
     @Test
     public void testGetSuggestions_withoutSmartSuggestions() {
         readAndDismissSuggestion(false);
-        mSuggestionParser.readSuggestions(mSuggestioCategory, mSuggestionsAfterDismiss, false);
-        assertThat(mSuggestionsBeforeDismiss.size()).isEqualTo(2);
-        assertThat(mSuggestionsAfterDismiss.size()).isEqualTo(1);
+        mSuggestionParser.readSuggestions(mMultipleCategory, mSuggestionsAfterDismiss, false);
+        assertThat(mSuggestionsBeforeDismiss).hasSize(2);
+        assertThat(mSuggestionsAfterDismiss).hasSize(1);
         assertThat(mSuggestionsBeforeDismiss.get(1)).isEqualTo(mSuggestionsAfterDismiss.get(0));
     }
 
     @Test
     public void testGetSuggestions_withSmartSuggestions() {
         readAndDismissSuggestion(true);
-        assertThat(mSuggestionsBeforeDismiss.size()).isEqualTo(2);
-        assertThat(mSuggestionsAfterDismiss.size()).isEqualTo(2);
+        assertThat(mSuggestionsBeforeDismiss).hasSize(2);
+        assertThat(mSuggestionsAfterDismiss).hasSize(2);
         assertThat(mSuggestionsBeforeDismiss).isEqualTo(mSuggestionsAfterDismiss);
     }
 
+    @Test
+    public void testGetSuggestion_exclusiveNotAvailable() {
+        RuntimeEnvironment.getRobolectricPackageManager().removeResolveInfosForIntent(
+                new Intent(Intent.ACTION_MAIN).addCategory("category2"),
+                "pkg3");
+
+        // If exclusive item is not available, the other categories should be shown
+        final List<Tile> suggestions = mSuggestionParser.getSuggestions();
+        assertThat(suggestions).hasSize(2);
+        assertThat(suggestions.get(0).category).isEqualTo("category1");
+        assertThat(suggestions.get(1).category).isEqualTo("category1");
+    }
+
+    @Test
+    public void testGetSuggestions_exclusive() {
+        final List<Tile> suggestions = mSuggestionParser.getSuggestions();
+        assertThat(suggestions).hasSize(1);
+        assertThat(suggestions.get(0).category).isEqualTo("category2");
+    }
+
     private void readAndDismissSuggestion(boolean isSmartSuggestionEnabled) {
-        mSuggestionsBeforeDismiss = new ArrayList<Tile>();
-        mSuggestionsAfterDismiss = new ArrayList<Tile>();
+        mSuggestionsBeforeDismiss = new ArrayList<>();
+        mSuggestionsAfterDismiss = new ArrayList<>();
         mSuggestionParser.readSuggestions(
-            mSuggestioCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled);
-        if (mSuggestionParser.dismissSuggestion(
-            mSuggestionsBeforeDismiss.get(0), isSmartSuggestionEnabled)) {
-            mInfo.remove(0);
+                mMultipleCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled);
+        final Tile suggestion = mSuggestionsBeforeDismiss.get(0);
+        if (mSuggestionParser.dismissSuggestion(suggestion, isSmartSuggestionEnabled)) {
+            RuntimeEnvironment.getRobolectricPackageManager().removeResolveInfosForIntent(
+                    new Intent(Intent.ACTION_MAIN).addCategory(suggestion.category),
+                    suggestion.intent.getComponent().getPackageName());
         }
         mSuggestionParser.readSuggestions(
-            mSuggestioCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
+                mMultipleCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
+    }
+
+    private static class TestPackageManager extends DefaultPackageManager {
+
+        TestPackageManager(ResourceLoader appResourceLoader) {
+            super(appResourceLoader);
+        }
+
+        @Override
+        public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
+            return super.queryIntentActivities(intent, flags);
+        }
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 2d3c4a7..dfbe43b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -16,11 +16,22 @@
 
 package com.android.settingslib.drawer;
 
-import android.app.ActivityManager;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
-import android.content.IContentProvider;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.IContentProvider;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -40,34 +51,23 @@
 
 import com.android.settingslib.SuggestionParser;
 import com.android.settingslib.TestConfig;
-import com.android.settingslib.drawer.TileUtilsTest;
-import static org.mockito.Mockito.atLeastOnce;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.shadows.ShadowApplication;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import org.mockito.ArgumentCaptor;
-
 
 @RunWith(RobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -179,7 +179,11 @@
                 false /* checkCategory */);
 
         assertThat(outTiles.size()).isEqualTo(1);
-        SuggestionParser parser = new SuggestionParser(mContext, null);
+        SuggestionParser parser = new SuggestionParser(
+                mContext,
+                null,
+                Collections.emptyList(),
+                "0,10");
         parser.filterSuggestions(outTiles, 0, false);
         assertThat(outTiles.size()).isEqualTo(0);
     }
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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e4d71b6..1147f16 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -181,7 +181,7 @@
     <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
 
     <!-- to control accessibility volume -->
-    <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
+    <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
 
     <application
         android:name=".SystemUIApplication"
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/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index 25d5226..1c087b3 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -43,6 +43,8 @@
         android:alpha="0"
         android:background="@color/qs_detail_progress_track"
         android:src="@drawable/indeterminate_anim"
+        android:scaleType="fitXY"
+        android:translationY="16dp"
         />
 
     <com.android.systemui.qs.NonInterceptingScrollView
@@ -57,6 +59,6 @@
             android:layout_height="match_parent"/>
     </com.android.systemui.qs.NonInterceptingScrollView>
 
-    <include layout="@layout/qs_detail_buttons"/>
+    <include layout="@layout/qs_detail_buttons" />
 
 </com.android.systemui.qs.QSDetail>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 64cac3c..f15475c 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -106,7 +106,7 @@
 
     <!-- The default tiles to display in QuickSettings -->
     <string name="quick_settings_tiles_default" translatable="false">
-        wifi,cell,bt,dnd,flashlight,rotation,battery,airplane
+        wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
     </string>
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
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/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index d058e78..79190cb 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -24,6 +24,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.NightDisplayController;
+import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.Preconditions;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.assist.AssistManager;
@@ -257,6 +258,8 @@
         mProviders.put(VolumeDialogController.class, () ->
                 new VolumeDialogControllerImpl(mContext));
 
+        mProviders.put(MetricsLogger.class, () -> new MetricsLogger());
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 9a4179f..6571294 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -405,17 +405,17 @@
                     }
                 } else {
                     drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
+                    if (FIXED_SIZED_SURFACE) {
+                        // If the surface is fixed-size, we should only need to
+                        // draw it once and then we'll let the window manager
+                        // position it appropriately.  As such, we no longer needed
+                        // the loaded bitmap.  Yay!
+                        // hw-accelerated renderer retains bitmap for faster rotation
+                        unloadWallpaper(false /* forgetSize */);
+                    }
                 }
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-                if (FIXED_SIZED_SURFACE && !mIsHwAccelerated) {
-                    // If the surface is fixed-size, we should only need to
-                    // draw it once and then we'll let the window manager
-                    // position it appropriately.  As such, we no longer needed
-                    // the loaded bitmap.  Yay!
-                    // hw-accelerated renderer retains bitmap for faster rotation
-                    unloadWallpaper(false /* forgetSize */);
-                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index ebda2e8..ec80745 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -45,14 +45,12 @@
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -61,6 +59,7 @@
 import com.android.systemui.R;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -128,8 +127,9 @@
                     break;
                 case MESSAGE_UPDATE_ACTIONS: {
                     final Bundle data = (Bundle) msg.obj;
-                    setActions(data.getParcelable(EXTRA_STACK_BOUNDS),
-                            ((ParceledListSlice) data.getParcelable(EXTRA_ACTIONS)).getList());
+                    final ParceledListSlice actions = data.getParcelable(EXTRA_ACTIONS);
+                    setActions(data.getParcelable(EXTRA_STACK_BOUNDS), actions != null
+                            ? actions.getList() : Collections.EMPTY_LIST);
                     break;
                 }
                 case MESSAGE_UPDATE_DISMISS_FRACTION: {
@@ -260,6 +260,7 @@
             }
             notifyMenuVisibility(true);
             updateExpandButtonFromBounds(stackBounds, movementBounds);
+            setDecorViewVisibility(true);
             mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
                     mMenuContainer.getAlpha(), 1f);
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
@@ -300,9 +301,7 @@
                     if (animationFinishedRunnable != null) {
                         animationFinishedRunnable.run();
                     }
-                    if (getSystemService(AccessibilityManager.class).isEnabled()) {
-                        finish();
-                    }
+                    setDecorViewVisibility(false);
                 }
             });
             mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener);
@@ -411,6 +410,7 @@
     }
 
     private void updateDismissFraction(float fraction) {
+        setDecorViewVisibility(true);
         int alpha;
         if (mMenuVisible) {
             mMenuContainer.setAlpha(1-fraction);
@@ -497,4 +497,16 @@
         v.removeCallbacks(mFinishRunnable);
         v.postDelayed(mFinishRunnable, delay);
     }
+
+    /**
+     * Sets the visibility of the root view of the window to disable drawing and touches for the
+     * activity.  This differs from {@link Activity#setVisible(boolean)} in that it does not set
+     * the internal mVisibleFromClient state.
+     */
+    private void setDecorViewVisibility(boolean visible) {
+        final View decorView = getWindow().getDecorView();
+        if (decorView != null) {
+            decorView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index f70d5b4..a0f491f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -37,8 +37,10 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.tuner.TunerService;
 
 import java.io.PrintWriter;
 
@@ -46,9 +48,11 @@
  * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
  * the PIP.
  */
-public class PipTouchHandler {
+public class PipTouchHandler implements TunerService.Tunable {
     private static final String TAG = "PipTouchHandler";
 
+    private static final String TUNER_KEY_MINIMIZE = "pip_minimize";
+
     // These values are used for metrics and should never change
     private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
     private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1;
@@ -97,6 +101,9 @@
                 }
             };
 
+    // Allow the PIP to be dragged to the edge of the screen to be minimized.
+    private boolean mEnableMinimize = false;
+
     // Behaviour states
     private boolean mIsMenuVisible;
     private boolean mIsMinimized;
@@ -104,6 +111,8 @@
     private int mImeHeight;
     private float mSavedSnapFraction = -1f;
     private boolean mSendingHoverAccessibilityEvents;
+    private boolean mMovementWithinMinimize;
+    private boolean mMovementWithinDismiss;
 
     // Touch state
     private final PipTouchState mTouchState;
@@ -167,6 +176,9 @@
         mExpandedShortestEdgeSize = context.getResources().getDimensionPixelSize(
                 R.dimen.pip_expanded_shortest_edge_size);
 
+        // Register any tuner settings changes
+        Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_MINIMIZE);
+
         // Register the listener for input consumer touch events
         inputConsumerController.setTouchListener(this::handleTouchEvent);
         inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
@@ -187,6 +199,20 @@
         }
     }
 
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (newValue == null) {
+            // Reset back to default
+            mEnableMinimize = false;
+            return;
+        }
+        switch (key) {
+            case TUNER_KEY_MINIMIZE:
+                mEnableMinimize = Integer.parseInt(newValue) != 0;
+                break;
+        }
+    }
+
     public void onConfigurationChanged() {
         mMotionHelper.onConfigurationChanged();
         mMotionHelper.synchronizePinnedStackBounds();
@@ -366,6 +392,9 @@
      * Sets the minimized state.
      */
     void setMinimizedStateInternal(boolean isMinimized) {
+        if (!mEnableMinimize) {
+            return;
+        }
         setMinimizedState(isMinimized, false /* fromController */);
     }
 
@@ -373,6 +402,9 @@
      * Sets the minimized state.
      */
     void setMinimizedState(boolean isMinimized, boolean fromController) {
+        if (!mEnableMinimize) {
+            return;
+        }
         if (mIsMinimized != isMinimized) {
             MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED,
                     isMinimized);
@@ -435,6 +467,8 @@
      * Gesture controlling normal movement of the PIP.
      */
     private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
+        // Whether the PiP was on the left side of the screen at the start of the gesture
+        private boolean mStartedOnLeft;
 
         @Override
         public void onDown(PipTouchState touchState) {
@@ -442,6 +476,10 @@
                 return;
             }
 
+            mStartedOnLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
+            mMovementWithinMinimize = true;
+            mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
+
             // If the menu is still visible, and we aren't minimized, then just poke the menu
             // so that it will timeout after the user stops touching it
             if (mMenuController.isMenuVisible() && !mIsMinimized) {
@@ -475,7 +513,7 @@
                 final PointF lastDelta = touchState.getLastTouchDelta();
                 float left = mTmpBounds.left + lastDelta.x;
                 float top = mTmpBounds.top + lastDelta.y;
-                if (!touchState.allowDraggingOffscreen()) {
+                if (!touchState.allowDraggingOffscreen() || !mEnableMinimize) {
                     left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left));
                 }
                 if (ENABLE_DISMISS_DRAG_TO_EDGE) {
@@ -493,6 +531,18 @@
                 if (ENABLE_DISMISS_DRAG_TO_EDGE) {
                     updateDismissFraction();
                 }
+
+                final PointF curPos = touchState.getLastTouchPosition();
+                if (mMovementWithinMinimize) {
+                    // Track if movement remains near starting edge to identify swipes to minimize
+                    mMovementWithinMinimize = mStartedOnLeft
+                            ? curPos.x <= mMovementBounds.left + mTmpBounds.width()
+                            : curPos.x >= mMovementBounds.right;
+                }
+                if (mMovementWithinDismiss) {
+                    // Track if movement remains near the bottom edge to identify swipe to dismiss
+                    mMovementWithinDismiss = curPos.y >= mMovementBounds.bottom;
+                }
                 return true;
             }
             return false;
@@ -526,8 +576,15 @@
             }
 
             if (touchState.isDragging()) {
-                final boolean onLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
-                boolean isFlingToBot = isFlingTowardsEdge(touchState, 4 /* bottom */);
+                final PointF vel = touchState.getVelocity();
+                final float velocity = PointF.length(vel.x, vel.y);
+                final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
+                final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
+                final boolean isFlingToBot = isFling
+                        && !isHorizontal && mMovementWithinDismiss && vel.y > 0;
+                final boolean isFlingToEdge = isFling && isHorizontal && mMovementWithinMinimize
+                        && (mStartedOnLeft ? vel.x < 0 : vel.x > 0);
+
                 if (ENABLE_DISMISS_DRAG_TO_EDGE
                         && (mMotionHelper.shouldDismissPip() || isFlingToBot)) {
                     mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds(),
@@ -536,8 +593,8 @@
                             MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
                             METRIC_VALUE_DISMISSED_BY_DRAG);
                     return true;
-                } else if (!mIsMinimized && (mMotionHelper.shouldMinimizePip()
-                        || isFlingTowardsEdge(touchState, onLeft ? 2 : 3))) {
+                } else if (mEnableMinimize &&
+                        !mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) {
                     // Pip should be minimized
                     setMinimizedStateInternal(true);
                     if (mMenuController.isMenuVisible()) {
@@ -563,9 +620,7 @@
                     mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds);
                 }
 
-                final PointF vel = mTouchState.getVelocity();
-                final float velocity = PointF.length(vel.x, vel.y);
-                if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+                if (isFling) {
                     mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
                             mUpdateScrimListener);
                 } else {
@@ -585,42 +640,6 @@
     };
 
     /**
-     * @return whether the gesture ending in {@param vel} is fast enough to be a fling and towards
-     *         the provided {@param edge} where:
-     *
-     *         1 = top
-     *         2 = left
-     *         3 = right
-     *         4 = bottom
-     */
-    private boolean isFlingTowardsEdge(PipTouchState touchState, int edge) {
-        final PointF vel = touchState.getVelocity();
-        final PointF downPos = touchState.getDownTouchPosition();
-        final Rect bounds = mMotionHelper.getBounds();
-        final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
-        final boolean isFling =
-                PointF.length(vel.x, vel.y) > mFlingAnimationUtils.getMinVelocityPxPerSecond();
-        if (!isFling) {
-            return false;
-        }
-        switch (edge) {
-            case 1: // top
-                return !isHorizontal && vel.y < 0
-                        && downPos.y <= mMovementBounds.top + bounds.height();
-            case 2: // left
-                return isHorizontal && vel.x < 0
-                        && downPos.x <= mMovementBounds.left + bounds.width();
-            case 3: // right
-                return isHorizontal && vel.x > 0
-                        && downPos.x >= mMovementBounds.right;
-            case 4: // bottom
-                return !isHorizontal && vel.y > 0
-                        && downPos.y >= mMovementBounds.bottom;
-        }
-        return false;
-    }
-
-    /**
      * Updates the current movement bounds based on whether the menu is currently visible.
      */
     private void updateMovementBounds(boolean isExpanded) {
@@ -643,6 +662,7 @@
         pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
         pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
         pw.println(innerPrefix + "mEnableDragToDismiss=" + ENABLE_DISMISS_DRAG_TO_TARGET);
+        pw.println(innerPrefix + "mEnableMinimize=" + mEnableMinimize);
         mSnapAlgorithm.dump(pw, innerPrefix);
         mTouchState.dump(pw, innerPrefix);
         mMotionHelper.dump(pw, innerPrefix);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a30b03b..6b50764 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -230,12 +230,8 @@
                 firstPageBuilder.addFloat(brightness, "translationY", heightDiff, 0);
                 mBrightnessAnimator = new TouchAnimator.Builder()
                         .addFloat(brightness, "alpha", 0, 1)
-                        .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
-                        .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
                         .setStartDelay(.5f)
                         .build();
-                mAllViews.add(mQsPanel.getPageIndicator());
-                mAllViews.add(mQsPanel.getDivider());
                 mAllViews.add(brightness);
             } else {
                 mBrightnessAnimator = null;
@@ -247,7 +243,11 @@
             mFirstPageDelayedAnimator = new TouchAnimator.Builder()
                     .setStartDelay(EXPANDED_TILE_DELAY)
                     .addFloat(tileLayout, "alpha", 0, 1)
+                    .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
+                    .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
                     .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
+            mAllViews.add(mQsPanel.getPageIndicator());
+            mAllViews.add(mQsPanel.getDivider());
             mAllViews.add(mQsPanel.getFooter().getView());
             float px = 0;
             float py = 1;
@@ -264,6 +264,8 @@
         }
         mNonfirstPageAnimator = new TouchAnimator.Builder()
                 .addFloat(mQuickQsPanel, "alpha", 1, 0)
+                .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
+                .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
                 .setListener(mNonFirstPageListener)
                 .setEndDelay(.5f)
                 .build();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 1709718..9efe224 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.qs;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
@@ -197,7 +199,7 @@
             mDetailContent.removeAllViews();
             mDetailContent.addView(detailView);
             mDetailViews.put(viewCacheIndex, detailView);
-            MetricsLogger.visible(mContext, adapter.getMetricsCategory());
+            Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
             announceForAccessibility(mContext.getString(
                     R.string.accessibility_quick_settings_detail,
                     adapter.getTitle()));
@@ -206,7 +208,7 @@
             setVisibility(View.VISIBLE);
         } else {
             if (mDetailAdapter != null) {
-                MetricsLogger.hidden(mContext, mDetailAdapter.getMetricsCategory());
+                Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
             }
             mClosingDetail = true;
             mDetailAdapter = null;
@@ -238,8 +240,12 @@
     protected void setupDetailFooter(DetailAdapter adapter) {
         final Intent settingsIntent = adapter.getSettingsIntent();
         mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
-        mDetailSettingsButton.setOnClickListener(v -> Dependency.get(ActivityStarter.class)
-                .postStartActivityDismissingKeyguard(settingsIntent, 0));
+        mDetailSettingsButton.setOnClickListener(v -> {
+            Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
+                    mDetailAdapter.getMetricsCategory());
+            Dependency.get(ActivityStarter.class)
+                    .postStartActivityDismissingKeyguard(settingsIntent, 0);
+        });
     }
 
     protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 2202b58..d51fe8a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
+
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -49,6 +51,7 @@
 import com.android.systemui.statusbar.phone.ExpandableIndicator;
 import com.android.systemui.statusbar.phone.MultiUserSwitch;
 import com.android.systemui.statusbar.phone.SettingsButton;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -160,11 +163,10 @@
         final Builder builder = new Builder()
                 .addFloat(mSettingsContainer, "translationX", -(remaining - defSpace), 0)
                 .addFloat(mSettingsButton, "rotation", -120, 0)
-                .addFloat(mAlarmStatus, "alpha", 0, 1)
-                .addFloat(mAlarmStatus, "translationX", 0, -mDate.getWidth())
-                .addFloat(mAlarmStatusCollapsed, "translationX", 0, -mDate.getWidth());
+                .addFloat(mAlarmStatus, "alpha", 0, 1);
         if (mAlarmShowing) {
-            builder.addFloat(mDate, "alpha", 1, 0);
+            builder.addFloat(mDate, "alpha", 1, 0)
+                    .addFloat(mDateTimeGroup, "translationX", 0, -mDate.getWidth());
         }
         mAnimator = builder.build();
         setExpansion(mExpansionAmount);
@@ -336,6 +338,11 @@
     @Override
     public void onClick(View v) {
         if (v == mSettingsButton) {
+            if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
+                // If user isn't setup just unlock the device and dump them back at SUW.
+                mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
+                return;
+            }
             MetricsLogger.action(mContext,
                     mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
                             : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
@@ -358,6 +365,8 @@
                 startSettingsActivity();
             }
         } else if (v == mDateTimeGroup) {
+            Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
+                    mNextAlarm != null);
             if (mNextAlarm != null) {
                 PendingIntent showIntent = mNextAlarm.getShowIntent();
                 mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 63563b2..406f107 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -21,6 +21,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -39,6 +40,8 @@
 public class QSFragment extends Fragment implements QS {
     private static final String TAG = "QS";
     private static final boolean DEBUG = false;
+    private static final String EXTRA_EXPANDED = "expanded";
+    private static final String EXTRA_LISTENING = "listening";
 
     private final Rect mQsBounds = new Rect();
     private boolean mQsExpanded;
@@ -85,6 +88,35 @@
 
         mQSCustomizer = view.findViewById(R.id.qs_customize);
         mQSCustomizer.setQs(this);
+        if (savedInstanceState != null) {
+            setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
+            setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mListening) {
+            setListening(false);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
+        outState.putBoolean(EXTRA_LISTENING, mListening);
+    }
+
+    @VisibleForTesting
+    boolean isListening() {
+        return mListening;
+    }
+
+    @VisibleForTesting
+    boolean isExpanded() {
+        return mQsExpanded;
     }
 
     @Override
@@ -221,7 +253,7 @@
         }
 
         // Set bounds on the QS panel so it doesn't run over the header.
-        mQsBounds.top = (int) (mHeader.getBottom() * (1 - expansion));
+        mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion));
         mQsBounds.right = mQSPanel.getWidth();
         mQsBounds.bottom = mQSPanel.getHeight();
         mQSPanel.setClipBounds(mQsBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 29d547c..8596b57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -32,6 +32,8 @@
     TileServices getTileServices();
     void removeTile(String tileSpec);
 
+    int indexOf(String tileSpec);
+
     interface Callback {
         void onTilesChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8298cbb..2e6116d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -59,6 +59,7 @@
     protected final View mBrightnessView;
     private final H mHandler = new H();
     private final View mPageIndicator;
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     private int mPanelPaddingBottom;
     private int mBrightnessPaddingTop;
@@ -259,7 +260,7 @@
         if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
             ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
         }
-        MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
+        mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
         if (!mExpanded) {
             closeDetail();
         } else {
@@ -475,7 +476,7 @@
         int newVis = visible ? VISIBLE : INVISIBLE;
         setVisibility(newVis);
         if (mGridContentVisible != visible) {
-            MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
+            mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
         }
         mGridContentVisible = visible;
     }
@@ -483,7 +484,7 @@
     private void logTiles() {
         for (int i = 0; i < mRecords.size(); i++) {
             TileRecord tileRecord = mRecords.get(i);
-            MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
+            mMetricsLogger.visible(tileRecord.tile.getMetricsCategory());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0ca115e..9330541 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -157,6 +157,10 @@
         return mServices;
     }
 
+    public int indexOf(String spec) {
+        return mTileSpecs.indexOf(spec);
+    }
+
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (!TILES_SETTING.equals(key)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 6f35017..b5c1bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -22,6 +22,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
@@ -155,6 +156,11 @@
         return mComponent;
     }
 
+    @Override
+    protected LogMaker populate(LogMaker logMaker) {
+        return super.populate(logMaker).setComponentName(mComponent);
+    }
+
     public Tile getQsTile() {
         return mTile;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index a751ef4..6e2add4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -84,12 +84,14 @@
 
     protected void updateIcon(ImageView iv, State state) {
         if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) {
+            boolean shouldAnimate = iv.isShown() && mAnimationEnabled
+                    && iv.getDrawable() != null;
             Drawable d = state.icon != null
-                    ? iv.isShown() && mAnimationEnabled ? state.icon.getDrawable(mContext)
+                    ? shouldAnimate ? state.icon.getDrawable(mContext)
                     : state.icon.getInvisibleDrawable(mContext) : null;
             int padding = state.icon != null ? state.icon.getPadding() : 0;
             if (d != null) {
-                d.setAutoMirrored(true);
+                d.setAutoMirrored(false);
             }
             iv.setImageDrawable(d);
             iv.setTag(R.id.qs_icon_tag, state.icon);
@@ -114,7 +116,7 @@
         if (state.state != mState) {
             int color = getColor(state.state);
             mState = state.state;
-            if (iv.isShown()) {
+            if (iv.isShown() && mTint != 0) {
                 animateGrayScale(mTint, color, iv);
                 mTint = color;
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 948954c2..1aa51b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -14,12 +14,19 @@
 
 package com.android.systemui.qs.tileimpl;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -29,7 +36,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
@@ -58,6 +64,7 @@
     protected final H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
     protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
     private final ArraySet<Object> mListeners = new ArraySet<>();
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     protected TState mState = newTileState();
@@ -76,7 +83,7 @@
     /**
      * Declare the category of this tile.
      *
-     * Categories are defined in {@link com.android.internal.logging.MetricsProto.MetricsEvent}
+     * Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}
      * by editing frameworks/base/proto/src/metrics_constants.proto.
      */
     abstract public int getMetricsCategory();
@@ -152,17 +159,28 @@
     }
 
     public void click() {
+        mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)));
         mHandler.sendEmptyMessage(H.CLICK);
     }
 
     public void secondaryClick() {
+        mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)));
         mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
     }
 
     public void longClick() {
+        mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)));
         mHandler.sendEmptyMessage(H.LONG_CLICK);
     }
 
+    protected LogMaker populate(LogMaker logMaker) {
+        if (mState instanceof BooleanState) {
+            logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0);
+        }
+        return logMaker.setSubtype(getMetricsCategory())
+                .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));
+    }
+
     public void showDetail(boolean show) {
         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
     }
@@ -224,7 +242,6 @@
     }
 
     protected void handleLongClick() {
-        MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
         Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
                 getLongClickIntent(), 0);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index c7979d8..8209ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -41,8 +41,8 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.qs.QSHost;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
 
@@ -102,7 +102,7 @@
 
     @Override
     public CharSequence getTileLabel() {
-        return mContext.getString(R.string.battery);
+        return mContext.getString(R.string.battery_detail_switch_title);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index ed6e6ef..4e4de15 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -83,7 +83,6 @@
     protected void handleClick() {
         // Secondary clicks are header clicks, just toggle.
         final boolean isEnabled = (Boolean)mState.value;
-        MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
         mController.setBluetoothEnabled(!isEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index a1d3d26..22b6a63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -108,13 +108,11 @@
     protected void handleClick() {
         if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
             mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
-                MetricsLogger.action(mContext, getMetricsCategory());
                 showDetail(true);
                 mHost.openPanels();
             });
             return;
         }
-        MetricsLogger.action(mContext, getMetricsCategory());
         showDetail(true);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 4351b2c..04be7de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -98,7 +98,6 @@
 
     @Override
     protected void handleSecondaryClick() {
-        MetricsLogger.action(mContext, getMetricsCategory());
         if (mDataController.isMobileDataSupported()) {
             showDetail(true);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index e33b680..5b374b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -84,7 +84,6 @@
 
     @Override
     protected void handleClick() {
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         mSetting.setValue(mState.value ? 0 : 1);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7a25140..b796451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -87,7 +87,6 @@
 
     private void toggleDataSaver() {
         mState.value = !mDataSaverController.isDataSaverEnabled();
-        MetricsLogger.action(mContext, getMetricsCategory(), mState.value);
         mDataSaverController.setDataSaverEnabled(mState.value);
         refreshState(mState.value);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f35de68..3c2e897 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -146,7 +146,6 @@
                     Toast.LENGTH_LONG).show();
             return;
         }
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         showDetail(true);
         int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
         mController.setZen(zen, null, TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 7b0fd73..6d2aa90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -87,7 +87,6 @@
         if (ActivityManager.isUserAMonkey()) {
             return;
         }
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         boolean newState = !mState.value;
         refreshState(newState);
         mFlashlightController.setFlashlight(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 6662937..5c3f65c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -106,7 +106,6 @@
         if (!isEnabled && mAirplaneMode.getValue() != 0) {
             return;
         }
-        MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
         mController.setHotspotEnabled(!isEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index c953363..00cfbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -92,7 +92,6 @@
 
     @Override
     protected void handleClick() {
-        MetricsLogger.action(mContext, getMetricsCategory(), mIntentPackage);
         sendIntent("click", mOnClick, mOnClickUri);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index b5c02cb..b11b15a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -81,13 +81,11 @@
             Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
                 final boolean wasEnabled = mState.value;
                 mHost.openPanels();
-                MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
                 mController.setLocationEnabled(!wasEnabled);
             });
             return;
         }
         final boolean wasEnabled = mState.value;
-        MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
         mController.setLocationEnabled(!wasEnabled);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index 3299339..d147b69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -85,7 +85,6 @@
     @Override
     protected void handleClick() {
         if (mAdapter == null) return;
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         if (!mAdapter.isEnabled()) {
             mAdapter.enable();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 8b47216..8aa1e43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -54,7 +54,6 @@
     @Override
     protected void handleClick() {
         final boolean activated = !mState.value;
-        MetricsLogger.action(mContext, getMetricsCategory(), activated);
         mController.setActivated(activated);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 130304f..fb937bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -79,7 +79,6 @@
     @Override
     protected void handleClick() {
         if (mController == null) return;
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         final boolean newState = !mState.value;
         mController.setRotationLocked(!newState);
         refreshState(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index fde2e04..79b4c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -114,7 +114,6 @@
     protected void handleClick() {
         // Secondary clicks are header clicks, just toggle.
         mState.copyTo(mStateBeforeClick);
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         mController.setWifiEnabled(!mState.value);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 5086091..6c89241 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -68,7 +68,6 @@
 
     @Override
     public void handleClick() {
-        MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         mProfileController.setWorkModeEnabled(!mState.value);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/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/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index b5f56c3..4d99a46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -22,6 +22,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 
@@ -33,7 +34,7 @@
     private ArrayMap<Integer, Integer> mLegacyMap;
     private LogMaker mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
             .setType(MetricsEvent.TYPE_ACTION);
-    private MetricsLogger mMetricsLogger = new MetricsLogger();
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     public LockscreenGestureLogger() {
         mLegacyMap = new ArrayMap<>(EventLogConstants.METRICS_GESTURE_TYPE_MAP.length);
@@ -58,9 +59,4 @@
         }
         return value;
     }
-
-    @VisibleForTesting
-    void setMetricsLogger(MetricsLogger metricsLogger) {
-        mMetricsLogger = metricsLogger;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 3f7e340..a5d7c57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -417,7 +417,10 @@
             getHomeButton().setImageDrawable(mHomeDefaultIcon);
         }
 
-        final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
+        // The Accessibility button always overrides the appearance of the IME switcher
+        final boolean showImeButton =
+                !mShowAccessibilityButton && ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN)
+                        != 0);
         getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
         getImeSwitchButton().setImageDrawable(mImeIcon);
 
@@ -545,8 +548,9 @@
         mShowAccessibilityButton = visible;
         mLongClickableAccessibilityButton = longClickable;
         if (visible) {
-            // Accessibility button overrides Menu button.
+            // Accessibility button overrides Menu and IME switcher buttons.
             setMenuVisibility(false, true);
+            getImeSwitchButton().setVisibility(View.INVISIBLE);
         }
 
         getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index bb6c8f2..53ec8c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -127,7 +127,6 @@
     private boolean mVolumeVisible;
     private boolean mCurrentUserSetup;
 
-    private boolean mManagedProfileFocused = false;
     private boolean mManagedProfileIconVisible = false;
     private boolean mManagedProfileInQuietMode = false;
 
@@ -439,45 +438,30 @@
         }
     }
 
-    private void profileChanged(int userId) {
-        UserInfo user = null;
-        if (userId == UserHandle.USER_CURRENT) {
-            try {
-                user = ActivityManager.getService().getCurrentUser();
-            } catch (RemoteException e) {
-                // Ignore
-            }
-        } else {
-            user = mUserManager.getUserInfo(userId);
-        }
-
-        mManagedProfileFocused = user != null && user.isManagedProfile();
-        if (DEBUG) Log.v(TAG, "profileChanged: mManagedProfileFocused: " + mManagedProfileFocused);
-        // Actually update the icon later when transition starts.
-    }
-
     private void updateManagedProfile() {
-        if (DEBUG) {
-            Log.v(TAG, "updateManagedProfile: mManagedProfileFocused: "
-                    + mManagedProfileFocused);
-        }
-        final boolean showIcon;
-        if (mManagedProfileFocused && !mKeyguardMonitor.isShowing()) {
-            showIcon = true;
-            mIconController.setIcon(mSlotManagedProfile,
-                    R.drawable.stat_sys_managed_profile_status,
-                    mContext.getString(R.string.accessibility_managed_profile));
-        } else if (mManagedProfileInQuietMode) {
-            showIcon = true;
-            mIconController.setIcon(mSlotManagedProfile,
-                    R.drawable.stat_sys_managed_profile_status_off,
-                    mContext.getString(R.string.accessibility_managed_profile));
-        } else {
-            showIcon = false;
-        }
-        if (mManagedProfileIconVisible != showIcon) {
-            mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
-            mManagedProfileIconVisible = showIcon;
+        try {
+            final boolean showIcon;
+            final int userId = ActivityManager.getService().getLastResumedActivityUserId();
+            if (mUserManager.isManagedProfile(userId) && !mKeyguardMonitor.isShowing()) {
+                showIcon = true;
+                mIconController.setIcon(mSlotManagedProfile,
+                        R.drawable.stat_sys_managed_profile_status,
+                        mContext.getString(R.string.accessibility_managed_profile));
+            } else if (mManagedProfileInQuietMode) {
+                showIcon = true;
+                mIconController.setIcon(mSlotManagedProfile,
+                        R.drawable.stat_sys_managed_profile_status_off,
+                        mContext.getString(R.string.accessibility_managed_profile));
+            } else {
+                showIcon = false;
+            }
+            if (mManagedProfileIconVisible != showIcon) {
+                mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
+                mManagedProfileIconVisible = showIcon;
+            }
+        } catch (RemoteException ex) {
+            Log.w(TAG, "updateManagedProfile: ", ex);
+            // ignore
         }
     }
 
@@ -556,35 +540,16 @@
             new SynchronousUserSwitchObserver() {
                 @Override
                 public void onUserSwitching(int newUserId) throws RemoteException {
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mUserInfoController.reloadUserInfo();
-                        }
-                    });
+                    mHandler.post(() -> mUserInfoController.reloadUserInfo());
                 }
 
                 @Override
                 public void onUserSwitchComplete(int newUserId) throws RemoteException {
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            updateAlarm();
-                            profileChanged(newUserId);
-                            updateQuietState();
-                            updateManagedProfile();
-                            updateForegroundInstantApps();
-                        }
-                    });
-                }
-
-                @Override
-                public void onForegroundProfileSwitch(int newProfileId) {
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            profileChanged(newProfileId);
-                        }
+                    mHandler.post(() -> {
+                        updateAlarm();
+                        updateQuietState();
+                        updateManagedProfile();
+                        updateForegroundInstantApps();
                     });
                 }
             };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index b82b113..5370ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -484,7 +484,7 @@
 
     private ScreenPinningRequest mScreenPinningRequest;
 
-    MetricsLogger mMetricsLogger = new MetricsLogger();
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     // ensure quick settings is disabled until the current user makes it through the setup wizard
     private boolean mUserSetup = false;
@@ -748,12 +748,6 @@
     private NavigationBarFragment mNavigationBar;
     private View mNavigationBarView;
 
-    @VisibleForTesting
-    void setMetricsLogger(MetricsLogger metricsLogger) {
-        mMetricsLogger = metricsLogger;
-        mLockscreenGestureLogger.setMetricsLogger(metricsLogger);
-    }
-
     @Override
     public void start() {
         mNetworkController = Dependency.get(NetworkController.class);
@@ -993,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/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
index 21f9a79..cae76b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
@@ -24,6 +24,10 @@
     boolean isUserSetup(int currentUser);
     int getCurrentUser();
 
+    default boolean isCurrentUserSetup() {
+        return isUserSetup(getCurrentUser());
+    }
+
     interface DeviceProvisionedListener {
         default void onDeviceProvisionedChanged() { }
         default void onUserSwitched() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index 3ee01de..21a96e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
+import android.view.Gravity;
 
 /**
  * Drawable for {@link KeyButtonView}s which contains an asset for both normal mode and light
@@ -40,6 +41,9 @@
 
     private KeyButtonDrawable(Drawable[] drawables) {
         super(drawables);
+        for (int i = 0; i < drawables.length; i++) {
+            setLayerGravity(i, Gravity.CENTER);
+        }
         mutate();
         mHasDarkDrawable = drawables.length > 1;
         setDarkIntensity(0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
new file mode 100644
index 0000000..c67cccc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.ViewUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.DetailAdapter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSDetailTest extends SysuiTestCase {
+
+    private MetricsLogger mMetricsLogger;
+    private QSDetail mQsDetail;
+    private QSPanel mQsPanel;
+    private QuickStatusBarHeader mQuickHeader;
+    private ActivityStarter mActivityStarter;
+    private DetailAdapter mMockDetailAdapter;
+    private TestableLooper mTestableLooper;
+
+    @Before
+    public void setup() throws Exception {
+        mTestableLooper = TestableLooper.get(this);
+        mTestableLooper.runWithLooper(() -> {
+            mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+            mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
+            mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
+            mQsPanel = mock(QSPanel.class);
+            mQuickHeader = mock(QuickStatusBarHeader.class);
+            mQsDetail.setQsPanel(mQsPanel, mQuickHeader);
+
+            mMockDetailAdapter = mock(DetailAdapter.class);
+            when(mMockDetailAdapter.createDetailView(any(), any(), any()))
+                    .thenReturn(mock(View.class));
+        });
+    }
+
+    @Test
+    public void testShowDetail_Metrics() {
+        ViewUtils.attachView(mQsDetail);
+        mTestableLooper.processAllMessages();
+
+        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+        verify(mMetricsLogger).visible(eq(mMockDetailAdapter.getMetricsCategory()));
+        mQsDetail.handleShowingDetail(null, 0, 0, false);
+        verify(mMetricsLogger).hidden(eq(mMockDetailAdapter.getMetricsCategory()));
+
+        ViewUtils.detachView(mQsDetail);
+        mTestableLooper.processAllMessages();
+    }
+
+    @Test
+    public void testMoreSettingsButton() {
+        ViewUtils.attachView(mQsDetail);
+        mTestableLooper.processAllMessages();
+
+        mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+        mQsDetail.findViewById(android.R.id.button2).performClick();
+
+        int metricsCategory = mMockDetailAdapter.getMetricsCategory();
+        verify(mMetricsLogger).action(eq(ACTION_QS_MORE_SETTINGS), eq(metricsCategory));
+
+        verify(mActivityStarter).postStartActivityDismissingKeyguard(any(), anyInt());
+
+        ViewUtils.detachView(mQsDetail);
+        mTestableLooper.processAllMessages();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
new file mode 100644
index 0000000..778ab8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.systemui.R;
+import com.android.systemui.R.id;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSFooterTest extends LeakCheckedTest {
+
+    private QSFooter mFooter;
+    private ActivityStarter mActivityStarter;
+    private DeviceProvisionedController mDeviceProvisionedController;
+
+    @Before
+    public void setup() throws Exception {
+        injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
+        mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
+        mDeviceProvisionedController = mDependency.injectMockDependency(
+                DeviceProvisionedController.class);
+        TestableLooper.get(this).runWithLooper(() -> {
+            mFooter = (QSFooter) LayoutInflater.from(mContext).inflate(R.layout.qs_footer, null);
+        });
+    }
+
+    @Test
+    public void testSettings_UserNotSetup() {
+        View settingsButton = mFooter.findViewById(id.settings_button);
+        when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
+
+        mFooter.onClick(settingsButton);
+        // Verify Settings wasn't launched.
+        verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index deb31da..673ffc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -15,14 +15,19 @@
 package com.android.systemui.qs;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
+import android.app.FragmentController;
+import android.app.FragmentManagerNonConfig;
 import android.os.Looper;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.CarrierText;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
+import android.os.Parcelable;
 import android.testing.AndroidTestingRunner;
 
 import com.android.systemui.SysuiBaseFragmentTest;
@@ -44,12 +49,15 @@
 @RunWithLooper(setAsMainLooper = true)
 public class QSFragmentTest extends SysuiBaseFragmentTest {
 
+    private MetricsLogger mMockMetricsLogger;
+
     public QSFragmentTest() {
         super(QSFragment.class);
     }
 
     @Before
     public void addLeakCheckDependencies() {
+        mMockMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
                 new LayoutInflaterBuilder(mContext)
                         .replace("com.android.systemui.statusbar.policy.SplitClockView",
@@ -86,4 +94,23 @@
         host.destroy();
         processAllMessages();
     }
+
+    @Test
+    public void testSaveState() {
+        QSFragment qs = (QSFragment) mFragment;
+
+        mFragments.dispatchResume();
+        processAllMessages();
+
+        qs.setListening(true);
+        qs.setExpanded(true);
+        processAllMessages();
+        recreateFragment();
+        processAllMessages();
+
+        // Get the reference to the new fragment.
+        qs = (QSFragment) mFragment;
+        assertTrue(qs.isListening());
+        assertTrue(qs.isExpanded());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
new file mode 100644
index 0000000..4979684
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.customize.QSCustomizer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSPanelTest extends SysuiTestCase {
+
+    private MetricsLogger mMetricsLogger;
+    private QSPanel mQsPanel;
+    private QSTileHost mHost;
+    private QSCustomizer mCustomizer;
+
+    @Before
+    public void setup() throws Exception {
+        TestableLooper.get(this).runWithLooper(() -> {
+            mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+            mQsPanel = new QSPanel(mContext, null);
+            mHost = mock(QSTileHost.class);
+            when(mHost.getTiles()).thenReturn(Collections.emptyList());
+            mCustomizer = mock(QSCustomizer.class);
+            mQsPanel.setHost(mHost, mCustomizer);
+        });
+    }
+
+    @Test
+    public void testSetExpanded_Metrics() {
+        mQsPanel.setExpanded(true);
+        verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(true));
+        mQsPanel.setExpanded(false);
+        verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(false));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
new file mode 100644
index 0000000..59483f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.service.quicksettings.Tile;
+import android.testing.AndroidTestingRunner;
+import android.testing.UiThreadTest;
+import android.widget.ImageView;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile.Icon;
+import com.android.systemui.plugins.qs.QSTile.State;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+@RunWith(AndroidTestingRunner.class)
+@UiThreadTest
+public class QSIconViewImplTest extends SysuiTestCase {
+
+    private QSIconViewImpl mIconView;
+
+    @Before
+    public void setup() {
+        mIconView = new QSIconViewImpl(mContext);
+    }
+
+    @Test
+    public void testNoFirstAnimation() {
+        ImageView iv = mock(ImageView.class);
+        State s = new State();
+        when(iv.isShown()).thenReturn(true);
+
+        // No current icon, only the static drawable should be used.
+        s.icon = mock(Icon.class);
+        when(iv.getDrawable()).thenReturn(null);
+        mIconView.updateIcon(iv, s);
+        verify(s.icon, never()).getDrawable(any());
+        verify(s.icon).getInvisibleDrawable(any());
+
+        // Has icon, should use the standard (animated) form.
+        s.icon = mock(Icon.class);
+        when(iv.getDrawable()).thenReturn(mock(Drawable.class));
+        mIconView.updateIcon(iv, s);
+        verify(s.icon).getDrawable(any());
+        verify(s.icon, never()).getInvisibleDrawable(any());
+    }
+
+    @Test
+    public void testNoFirstFade() {
+        ImageView iv = mock(ImageView.class);
+        State s = new State();
+        s.state = Tile.STATE_ACTIVE;
+        int desiredColor = mIconView.getColor(s.state);
+        when(iv.isShown()).thenReturn(true);
+
+        mIconView.setIcon(iv, s);
+        verify(iv).setImageTintList(argThat(stateList -> stateList.getColors()[0] == desiredColor));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
new file mode 100644
index 0000000..9ed9d28c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.metrics.LogMaker;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QSTileHost;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSTileImplTest extends SysuiTestCase {
+
+    public static final int POSITION = 14;
+    private TestableLooper mTestableLooper;
+    private TileImpl mTile;
+    private QSTileHost mHost;
+    private MetricsLogger mMetricsLogger;
+
+    @Before
+    public void setup() throws Exception {
+        String spec = "spec";
+        mTestableLooper = TestableLooper.get(this);
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+        mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+        mHost = mock(QSTileHost.class);
+        when(mHost.indexOf(spec)).thenReturn(POSITION);
+        mTestableLooper.runWithLooper(() -> {
+            mTile = new TileImpl(mHost);
+            mTile.setTileSpec(spec);
+        });
+    }
+
+    @Test
+    public void testClick_Metrics() {
+        mTile.click();
+        verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_CLICK)));
+    }
+
+    @Test
+    public void testSecondaryClick_Metrics() {
+        mTile.secondaryClick();
+        verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
+    }
+
+    @Test
+    public void testLongClick_Metrics() {
+        mTile.longClick();
+        verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS)));
+    }
+
+    @Test
+    public void testPopulate() {
+        LogMaker maker = mock(LogMaker.class);
+        when(maker.setSubtype(anyInt())).thenReturn(maker);
+        mTile.getState().value = true;
+        mTile.populate(maker);
+        verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1));
+        verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION));
+    }
+
+    private class TileLogMatcher implements ArgumentMatcher<LogMaker> {
+
+        private final int mCategory;
+        public String mInvalid;
+
+        public TileLogMatcher(int category) {
+            mCategory = category;
+        }
+
+        @Override
+        public boolean matches(LogMaker arg) {
+            if (arg.getCategory() != mCategory) {
+                mInvalid = "Expected category " + mCategory + " but was " + arg.getCategory();
+                return false;
+            }
+            if (arg.getType() != TYPE_ACTION) {
+                mInvalid = "Expected type " + TYPE_ACTION + " but was " + arg.getType();
+                return false;
+            }
+            if (arg.getSubtype() != mTile.getMetricsCategory()) {
+                mInvalid = "Expected subtype " + mTile.getMetricsCategory() + " but was "
+                        + arg.getSubtype();
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return mInvalid;
+        }
+    }
+
+    private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
+        protected TileImpl(QSHost host) {
+            super(host);
+        }
+
+        @Override
+        public BooleanState newTileState() {
+            return new BooleanState();
+        }
+
+        @Override
+        protected void handleClick() {
+
+        }
+
+        @Override
+        protected void handleUpdateState(BooleanState state, Object arg) {
+
+        }
+
+        @Override
+        public int getMetricsCategory() {
+            return 42;
+        }
+
+        @Override
+        public Intent getLongClickIntent() {
+            return null;
+        }
+
+        @Override
+        protected void setListening(boolean listening) {
+
+        }
+
+        @Override
+        public CharSequence getTileLabel() {
+            return null;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index a9acda3..6ddbffc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -16,6 +16,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -104,6 +105,6 @@
 
         fragment.disable(0, 0, false);
 
-        Mockito.verify(mNotificationAreaInner).setVisibility(eq(View.VISIBLE));
+        Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index f48af75..bf6b394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -62,9 +62,9 @@
         mKeyguardIndicationController = mock(KeyguardIndicationController.class);
         mStackScroller = mock(NotificationStackScrollLayout.class);
         mMetricsLogger = new FakeMetricsLogger();
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
         mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
                 mKeyguardIndicationController, mStackScroller);
-        mStatusBar.setMetricsLogger(mMetricsLogger);
 
         doAnswer(invocation -> {
             OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 783aae7..da441f5 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3656,7 +3656,7 @@
     ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE = 870;
 
     // The name of the activity being launched in an app transition event.
-    APP_TRANSITION_ACTIVITY_NAME = 871;
+    FIELD_CLASS_NAME = 871;
 
     // ACTION: Settings > App detail > Uninstall
     ACTION_SETTINGS_UNINSTALL_APP = 872;
@@ -3874,6 +3874,24 @@
     // OPEN: Settings -> System -> Reset options
     RESET_DASHBOARD = 924;
 
+    // ACTION: QS -> Tile clicked
+    ACTION_QS_CLICK = 925;
+
+    // ACTION: QS -> Secondary click
+    ACTION_QS_SECONDARY_CLICK = 926;
+
+    // FIELD: Position info in QS clicks
+    FIELD_QS_POSITION = 927;
+
+    // FIELD: The value of a QS tile when clicked (if applicable)
+    FIELD_QS_VALUE = 928;
+
+    // ACTION: QS -> Detail panel -> more settings
+    ACTION_QS_MORE_SETTINGS = 929;
+
+    // ACTION: QS -> Click date
+    ACTION_QS_DATE = 930;
+
     // ---- End O Constants, all O constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 05c6592..acaae7b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -52,6 +52,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.input.InputManager;
+import android.media.AudioManagerInternal;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -75,6 +76,7 @@
 import android.provider.SettingsStringUtil.SettingStringHelper;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -220,6 +222,8 @@
     private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
             new ArrayList<>();
 
+    private final IntArray mTempIntArray = new IntArray(0);
+
     private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
             new RemoteCallbackList<>();
 
@@ -1558,6 +1562,21 @@
             }
         }
 
+        final int count = userState.mBoundServices.size();
+        mTempIntArray.clear();
+        for (int i = 0; i < count; i++) {
+            final ResolveInfo resolveInfo =
+                    userState.mBoundServices.get(i).mAccessibilityServiceInfo.getResolveInfo();
+            if (resolveInfo != null) {
+                mTempIntArray.add(resolveInfo.serviceInfo.applicationInfo.uid);
+            }
+        }
+        // Calling out with lock held, but to a lower-level service
+        final AudioManagerInternal audioManager =
+                LocalServices.getService(AudioManagerInternal.class);
+        if (audioManager != null) {
+            audioManager.setAccessibilityServiceUids(mTempIntArray);
+        }
         updateAccessibilityEnabledSetting(userState);
     }
 
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/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 037804e..57d3570 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3537,21 +3537,23 @@
                     return;
                 }
                 mCancelAll = cancelAll;
-                // Whoops, the current agent timed out running doBackup().  Tidy up and restage
-                // it for the next time we run a backup pass.
-                // !!! TODO: keep track of failure counts per agent, and blacklist those which
-                // fail repeatedly (i.e. have proved themselves to be buggy).
-                Slog.e(TAG, "Cancel backing up " + mCurrentPackage.packageName);
-                EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName);
+                final String logPackageName = (mCurrentPackage != null)
+                        ? mCurrentPackage.packageName
+                        : "no_package_yet";
+                Slog.i(TAG, "Cancel backing up " + logPackageName);
+                EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, logPackageName);
+                addBackupTrace("cancel of " + logPackageName + ", cancelAll=" + cancelAll);
                 mMonitor = monitorEvent(mMonitor,
                         BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
                         mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
                         putMonitoringExtra(null, BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL,
                                 mCancelAll));
-                addBackupTrace(
-                        "cancel of " + mCurrentPackage.packageName + ", cancelAll=" + cancelAll);
                 errorCleanup();
                 if (!cancelAll) {
+                    // The current agent either timed out or was cancelled running doBackup().
+                    // Restage it for the next time we run a backup pass.
+                    // !!! TODO: keep track of failure counts per agent, and blacklist those which
+                    // fail repeatedly (i.e. have proved themselves to be buggy).
                     executeNextState(
                             mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
                     dataChangedImpl(mCurrentPackage.packageName);
diff --git a/services/core/Android.mk b/services/core/Android.mk
index e35a171..099f557 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -22,7 +22,8 @@
     services.net \
     android.hardware.light@2.0-java \
     android.hardware.power@1.0-java \
-    android.hardware.tv.cec@1.0-java
+    android.hardware.tv.cec@1.0-java \
+    android.hidl.manager@1.0-java
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     tzdata_shared2 \
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d02b726..e791014 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -96,7 +96,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -208,6 +207,8 @@
     // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
     private final int mReleasePendingIntentDelayMs;
 
+    private MockableSystemProperties mSystemProperties;
+
     private Tethering mTethering;
 
     private final PermissionMonitor mPermissionMonitor;
@@ -691,6 +692,8 @@
             IpConnectivityLog logger) {
         if (DBG) log("ConnectivityService starting up");
 
+        mSystemProperties = getSystemProperties();
+
         mMetricsLog = logger;
         mDefaultRequest = createInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
         NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder());
@@ -708,7 +711,7 @@
         mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
 
-        mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
+        mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
 
         mContext = checkNotNull(context, "missing Context");
         mNetd = checkNotNull(netManager, "missing INetworkManagementService");
@@ -735,7 +738,7 @@
         mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
 
         // TODO: What is the "correct" way to do determine if this is a wifi only device?
-        boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
+        boolean wifiOnly = mSystemProperties.getBoolean("ro.radio.noril", false);
         log("wifiOnly=" + wifiOnly);
         String[] naStrings = context.getResources().getStringArray(
                 com.android.internal.R.array.networkAttributes);
@@ -788,8 +791,8 @@
             }
         }
 
-        mTestMode = SystemProperties.get("cm.test.mode").equals("true")
-                && SystemProperties.get("ro.build.type").equals("eng");
+        mTestMode = mSystemProperties.get("cm.test.mode").equals("true")
+                && mSystemProperties.get("ro.build.type").equals("eng");
 
         mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager,
                                    IoThread.get().getLooper(), new MockableSystemProperties());
@@ -1814,8 +1817,8 @@
 
     // Overridden for testing purposes to avoid writing to SystemProperties.
     @VisibleForTesting
-    protected int getDefaultTcpRwnd() {
-        return SystemProperties.getInt(DEFAULT_TCP_RWND_KEY, 0);
+    protected MockableSystemProperties getSystemProperties() {
+        return new MockableSystemProperties();
     }
 
     private void updateTcpBufferSizes(NetworkAgentInfo nai) {
@@ -1853,10 +1856,11 @@
         }
 
         Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(),
-            Settings.Global.TCP_DEFAULT_INIT_RWND, getDefaultTcpRwnd());
+            Settings.Global.TCP_DEFAULT_INIT_RWND,
+                    mSystemProperties.getInt("net.tcp.default_init_rwnd", 0));
         final String sysctlKey = "sys.sysctl.tcp_def_init_rwnd";
         if (rwndValue != 0) {
-            SystemProperties.set(sysctlKey, rwndValue.toString());
+            mSystemProperties.set(sysctlKey, rwndValue.toString());
         }
     }
 
@@ -1880,7 +1884,7 @@
 
     @Override
     public int getRestoreDefaultNetworkDelay(int networkType) {
-        String restoreDefaultNetworkDelayStr = SystemProperties.get(
+        String restoreDefaultNetworkDelayStr = mSystemProperties.get(
                 NETWORK_RESTORE_DELAY_PROP_NAME);
         if(restoreDefaultNetworkDelayStr != null &&
                 restoreDefaultNetworkDelayStr.length() != 0) {
@@ -3081,7 +3085,7 @@
     @Override
     public boolean isTetheringSupported() {
         enforceTetherAccessPermission();
-        int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
+        int defaultVal = (mSystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
         boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
@@ -4580,11 +4584,11 @@
             ++last;
             String key = "net.dns" + last;
             String value = dns.getHostAddress();
-            SystemProperties.set(key, value);
+            mSystemProperties.set(key, value);
         }
         for (int i = last + 1; i <= mNumDnsEntries; ++i) {
             String key = "net.dns" + i;
-            SystemProperties.set(key, "");
+            mSystemProperties.set(key, "");
         }
         mNumDnsEntries = last;
     }
diff --git a/services/core/java/com/android/server/FontManagerService.java b/services/core/java/com/android/server/FontManagerService.java
index 55a945a..f172647 100644
--- a/services/core/java/com/android/server/FontManagerService.java
+++ b/services/core/java/com/android/server/FontManagerService.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.FontListParser;
+import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.text.FontConfig;
 import android.util.Slog;
@@ -34,6 +35,7 @@
 public class FontManagerService extends IFontManager.Stub {
     private static final String TAG = "FontManagerService";
     private static final String FONTS_CONFIG = "/system/etc/fonts.xml";
+    private static final String SYSTEM_FONT_DIR = "/system/fonts/";
 
     @GuardedBy("mLock")
     private FontConfig mConfig;
@@ -63,28 +65,22 @@
     public FontConfig getSystemFonts() {
         synchronized (mLock) {
             if (mConfig != null) {
-                return new FontConfig(mConfig);
+                return mConfig;
             }
 
-            FontConfig config = loadFromSystem();
-            if (config == null) {
+            mConfig = loadFromSystem();
+            if (mConfig == null) {
                 return null;
             }
 
-            for (FontConfig.Family family : config.getFamilies()) {
+            for (FontConfig.Family family : mConfig.getFamilies()) {
                 for (FontConfig.Font font : family.getFonts()) {
-                    File fontFile = new File(font.getFontName());
-                    try {
-                        font.setFd(ParcelFileDescriptor.open(
-                                fontFile, ParcelFileDescriptor.MODE_READ_ONLY));
-                    } catch (IOException e) {
-                        Slog.e(TAG, "Error opening font file " + font.getFontName(), e);
-                    }
+                    File fontFile = new File(SYSTEM_FONT_DIR, font.getFontName());
+                    font.setUri(Uri.fromFile(fontFile));
                 }
             }
 
-            mConfig = config;
-            return new FontConfig(mConfig);
+            return mConfig;
         }
     }
 
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 81219da..c68000a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -98,6 +98,7 @@
 import com.android.internal.app.IMediaContainerService;
 import com.android.internal.os.AppFuseMount;
 import com.android.internal.os.FuseAppLoop;
+import com.android.internal.os.FuseUnavailableMountException;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
@@ -2080,6 +2081,20 @@
                 Binder.restoreCallingIdentity(token);
             }
         }
+
+        if ((mask & StorageManager.DEBUG_VIRTUAL_DISK) != 0) {
+            final boolean enabled = (flags & StorageManager.DEBUG_VIRTUAL_DISK) != 0;
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                SystemProperties.set(StorageManager.PROP_VIRTUAL_DISK, Boolean.toString(enabled));
+
+                // Reset storage to kick new setting into place
+                mHandler.obtainMessage(H_RESET).sendToTarget();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
     }
 
     @Override
@@ -2993,32 +3008,36 @@
         }
     }
 
-    private ParcelFileDescriptor mountAppFuse(int uid, int mountId)
-            throws NativeDaemonConnectorException {
-        final NativeDaemonEvent event = StorageManagerService.this.mConnector.execute(
-                "appfuse", "mount", uid, Process.myPid(), mountId);
-        if (event.getFileDescriptors() == null ||
-            event.getFileDescriptors().length == 0) {
-            throw new NativeDaemonConnectorException("Cannot obtain device FD");
-        }
-        return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
-    }
-
     class AppFuseMountScope extends AppFuseBridge.MountScope {
-        public AppFuseMountScope(int uid, int pid, int mountId)
-                throws NativeDaemonConnectorException {
-            super(uid, pid, mountId, mountAppFuse(uid, mountId));
+        boolean opened = false;
+
+        public AppFuseMountScope(int uid, int pid, int mountId) {
+            super(uid, pid, mountId);
+        }
+
+        @Override
+        public ParcelFileDescriptor open() throws NativeDaemonConnectorException {
+            final NativeDaemonEvent event = StorageManagerService.this.mConnector.execute(
+                    "appfuse", "mount", uid, Process.myPid(), mountId);
+            opened = true;
+            if (event.getFileDescriptors() == null ||
+                event.getFileDescriptors().length == 0) {
+                throw new NativeDaemonConnectorException("Cannot obtain device FD");
+            }
+            return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
         }
 
         @Override
         public void close() throws Exception {
-            super.close();
-            mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
+            if (opened) {
+                mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
+                opened = false;
+            }
         }
     }
 
     @Override
-    public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+    public @Nullable AppFuseMount mountProxyFileDescriptorBridge() {
         Slog.v(TAG, "mountProxyFileDescriptorBridge");
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
@@ -3035,12 +3054,12 @@
                     final int name = mNextAppFuseName++;
                     try {
                         return new AppFuseMount(
-                            name,
-                            mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name)));
-                    } catch (AppFuseBridge.BridgeException e) {
+                            name, mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name)));
+                    } catch (FuseUnavailableMountException e) {
                         if (newlyCreated) {
                             // If newly created bridge fails, it's a real error.
-                            throw new RemoteException(e.getMessage());
+                            Slog.e(TAG, "", e);
+                            return null;
                         }
                         // It seems the thread of mAppFuseBridge has already been terminated.
                         mAppFuseBridge = null;
@@ -3053,19 +3072,21 @@
     }
 
     @Override
-    public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode)
-            throws RemoteException {
-        Slog.v(TAG, "mountProxyFileDescriptorBridge");
+    public @Nullable ParcelFileDescriptor openProxyFileDescriptor(
+            int mountId, int fileId, int mode) {
+        Slog.v(TAG, "mountProxyFileDescriptor");
         final int pid = Binder.getCallingPid();
         try {
             synchronized (mAppFuseLock) {
                 if (mAppFuseBridge == null) {
-                    throw new RemoteException("Cannot find mount point");
+                    Slog.e(TAG, "FuseBridge has not been created");
+                    return null;
                 }
                 return mAppFuseBridge.openFile(pid, mountId, fileId, mode);
             }
-        } catch (FileNotFoundException | SecurityException | InterruptedException error) {
-            throw new RemoteException(error.getMessage());
+        } catch (FuseUnavailableMountException | InterruptedException error) {
+            Slog.v(TAG, "The mount point has already been invalid", error);
+            return null;
         }
     }
 
diff --git a/services/core/java/com/android/server/UiThread.java b/services/core/java/com/android/server/UiThread.java
index 1bc6250..fd88d26 100644
--- a/services/core/java/com/android/server/UiThread.java
+++ b/services/core/java/com/android/server/UiThread.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.Trace;
 
@@ -26,20 +27,28 @@
  * on it to avoid UI jank.
  */
 public final class UiThread extends ServiceThread {
+    private static final long SLOW_DISPATCH_THRESHOLD_MS = 100;
     private static UiThread sInstance;
     private static Handler sHandler;
 
     private UiThread() {
         super("android.ui", Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
+    }
+
+    @Override
+    public void run() {
         // Make sure UiThread is in the fg stune boost group
         Process.setThreadGroup(Process.myTid(), Process.THREAD_GROUP_TOP_APP);
+        super.run();
     }
 
     private static void ensureThreadLocked() {
         if (sInstance == null) {
             sInstance = new UiThread();
             sInstance.start();
-            sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            final Looper looper = sInstance.getLooper();
+            looper.setTraceTag(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            looper.setSlowDispatchThresholdMs(SLOW_DISPATCH_THRESHOLD_MS);
             sHandler = new Handler(sInstance.getLooper());
         }
     }
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index ce4ca02..80f89fc 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.hidl.manager.V1_0.IServiceManager;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IPowerManager;
@@ -42,6 +43,9 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
 
 /** This class calls its monitor every minute. Killing this process if they don't return **/
 public class Watchdog extends Thread {
@@ -75,6 +79,14 @@
         "com.android.bluetooth",  // Bluetooth service
     };
 
+    public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
+        "android.hardware.audio@2.0::IDevicesFactory",
+        "android.hardware.bluetooth@1.0::IBluetoothHci",
+        "android.hardware.camera.provider@2.4::ICameraProvider",
+        "android.hardware.vr@1.0::IVr",
+        "android.hardware.media.omx@1.0::IOmx"
+    );
+
     static Watchdog sWatchdog;
 
     /* This handler will be used to post message back onto the main thread */
@@ -344,6 +356,43 @@
         return builder.toString();
     }
 
+    private ArrayList<Integer> getInterestingHalPids() {
+        try {
+            IServiceManager serviceManager = IServiceManager.getService();
+            ArrayList<IServiceManager.InstanceDebugInfo> dump =
+                    serviceManager.debugDump();
+            HashSet<Integer> pids = new HashSet<>();
+            for (IServiceManager.InstanceDebugInfo info : dump) {
+                if (info.pid == IServiceManager.PidConstant.NO_PID) {
+                    continue;
+                }
+
+                if (!HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName)) {
+                    continue;
+                }
+
+                pids.add(info.pid);
+            }
+            return new ArrayList<Integer>(pids);
+        } catch (RemoteException e) {
+            return new ArrayList<Integer>();
+        }
+    }
+
+    private ArrayList<Integer> getInterestingNativePids() {
+        ArrayList<Integer> pids = getInterestingHalPids();
+
+        int[] nativePids = Process.getPidsForCommands(NATIVE_STACKS_OF_INTEREST);
+        if (nativePids != null) {
+            pids.ensureCapacity(pids.size() + nativePids.length);
+            for (int i : nativePids) {
+                pids.add(i);
+            }
+        }
+
+        return pids;
+    }
+
     @Override
     public void run() {
         boolean waitedHalf = false;
@@ -400,7 +449,7 @@
                         ArrayList<Integer> pids = new ArrayList<Integer>();
                         pids.add(Process.myPid());
                         ActivityManagerService.dumpStackTraces(true, pids, null, null,
-                                NATIVE_STACKS_OF_INTEREST);
+                            getInterestingNativePids());
                         waitedHalf = true;
                     }
                     continue;
@@ -417,13 +466,13 @@
             // Then kill this process so that the system will restart.
             EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
 
-            ArrayList<Integer> pids = new ArrayList<Integer>();
+            ArrayList<Integer> pids = new ArrayList<>();
             pids.add(Process.myPid());
             if (mPhonePid > 0) pids.add(mPhonePid);
             // Pass !waitedHalf so that just in case we somehow wind up here without having
             // dumped the halfway stacks, we properly re-initialize the trace file.
             final File stack = ActivityManagerService.dumpStackTraces(
-                    !waitedHalf, pids, null, null, NATIVE_STACKS_OF_INTEREST);
+                    !waitedHalf, pids, null, null, getInterestingNativePids());
 
             // Give some extra time to make sure the stack traces get written.
             // The system's been hanging for a minute, another second or two won't hurt much.
diff --git a/services/core/java/com/android/server/accounts/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 3eb236e..8cb0eee 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;
@@ -3211,18 +3212,18 @@
             }
         }
 
-        mWindowManager.setFocusedApp(r.appToken, true);
-
-        applyUpdateLockStateLocked(r);
-        applyUpdateVrModeLocked(r);
         if (mLastResumedActivity != null && r.userId != mLastResumedActivity.userId) {
             mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
             mHandler.obtainMessage(
                     FOREGROUND_PROFILE_CHANGED_MSG, r.userId, 0).sendToTarget();
         }
-
         mLastResumedActivity = r;
 
+        mWindowManager.setFocusedApp(r.appToken, true);
+
+        applyUpdateLockStateLocked(r);
+        applyUpdateVrModeLocked(r);
+
         EventLogTags.writeAmSetResumedActivity(
                 r == null ? -1 : r.userId,
                 r == null ? "NULL" : r.shortComponentName,
@@ -3928,8 +3929,13 @@
             if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
                 // Debuggable apps may include a wrapper script with their library directory.
                 String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh";
-                if (new File(wrapperFileName).exists()) {
-                    invokeWith = "/system/bin/logwrapper " + wrapperFileName;
+                StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+                try {
+                    if (new File(wrapperFileName).exists()) {
+                        invokeWith = "/system/bin/logwrapper " + wrapperFileName;
+                    }
+                } finally {
+                    StrictMode.setThreadPolicy(oldPolicy);
                 }
             }
 
@@ -5468,11 +5474,12 @@
      *    appended to any existing file content.
      * @param firstPids of dalvik VM processes to dump stack traces for first
      * @param lastPids of dalvik VM processes to dump stack traces for last
-     * @param nativeProcs optional list of native process names to dump stack crawls
+     * @param nativePids optional list of native pids to dump stack crawls
      * @return file containing stack traces, or null if no dump file is configured
      */
     public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
+            ArrayList<Integer> nativePids) {
         String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
         if (tracesPath == null || tracesPath.length() == 0) {
             return null;
@@ -5488,7 +5495,7 @@
             return null;
         }
 
-        dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
+        dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativePids);
         return tracesFile;
     }
 
@@ -5530,7 +5537,8 @@
     }
 
     private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
+            ArrayList<Integer> nativePids) {
         // Use a FileObserver to detect when traces finish writing.
         // The order of traces is considered important to maintain for legibility.
         DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
@@ -5551,18 +5559,15 @@
             }
 
             // Next collect the stacks of the native pids
-            if (nativeProcs != null) {
-                int[] pids = Process.getPidsForCommands(nativeProcs);
-                if (pids != null) {
-                    for (int pid : pids) {
-                        if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
-                        final long sime = SystemClock.elapsedRealtime();
+            if (nativePids != null) {
+                for (int pid : nativePids) {
+                    if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
+                    final long sime = SystemClock.elapsedRealtime();
 
-                        Debug.dumpNativeBacktraceToFileTimeout(
-                                pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
-                        if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
-                                + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
-                    }
+                    Debug.dumpNativeBacktraceToFileTimeout(
+                            pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
+                    if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
+                            + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                 }
             }
 
@@ -18277,6 +18282,10 @@
             return record.info.isInstantApp();
         }
         // Otherwise check with PackageManager.
+        if (callerPackage == null) {
+            Slog.e(TAG, "isInstantApp with an application's uid, no record, and no package name");
+            throw new IllegalArgumentException("Calling application did not provide package name");
+        }
         mAppOpsService.checkPackage(uid, callerPackage);
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
@@ -23590,6 +23599,21 @@
         }
     }
 
+    /**
+     * Return the user id of the last resumed activity.
+     */
+    @Override
+    public @UserIdInt int getLastResumedActivityUserId() {
+        enforceCallingPermission(
+                permission.INTERACT_ACROSS_USERS_FULL, "getLastResumedActivityUserId()");
+        synchronized (this) {
+            if (mLastResumedActivity == null) {
+                return mUserController.getCurrentUserIdLocked();
+            }
+            return mLastResumedActivity.userId;
+        }
+    }
+
     private final class SleepTokenImpl extends SleepToken {
         private final String mTag;
         private final long mAcquireTime;
@@ -23876,6 +23900,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/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 04a09fe..5edfb06 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -10,8 +10,8 @@
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_ACTIVITY_NAME;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CALLING_PACKAGE_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CLASS_NAME;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_IS_EPHEMERAL;
@@ -313,7 +313,7 @@
             final LogMaker builder = new LogMaker(APP_TRANSITION);
             builder.setPackageName(info.launchedActivity.packageName);
             builder.setType(type);
-            builder.addTaggedData(APP_TRANSITION_ACTIVITY_NAME, info.launchedActivity.info.name);
+            builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name);
             if (info.launchedActivity.launchedFromPackage != null) {
                 builder.addTaggedData(APP_TRANSITION_CALLING_PACKAGE_NAME,
                         info.launchedActivity.launchedFromPackage);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9a4f804..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;
@@ -759,6 +760,12 @@
         }
     }
 
+    final void removeActivitiesFromLRUListLocked(TaskRecord task) {
+        for (ActivityRecord r : task.mActivities) {
+            mLRUActivities.remove(r);
+        }
+    }
+
     final boolean updateLRUListLocked(ActivityRecord r) {
         final boolean hadit = mLRUActivities.remove(r);
         mLRUActivities.add(r);
@@ -1179,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);
@@ -1207,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);
@@ -1237,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);
@@ -1268,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);
@@ -3482,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;
@@ -3523,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.
@@ -3535,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) {
@@ -3546,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);
             }
@@ -3565,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;
@@ -3858,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();
@@ -4917,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.
@@ -4930,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();
         }
 
@@ -4947,6 +4996,7 @@
             }
         }
         mTaskHistory.remove(task);
+        removeActivitiesFromLRUListLocked(task);
         updateTaskMovement(task, true);
 
         if (mode == REMOVE_TASK_MODE_DESTROYING && task.mActivities.isEmpty()) {
@@ -5114,6 +5164,7 @@
         // Apps may depend on onResume()/onPause() being called in pairs.
         if (setResume) {
             mResumedActivity = r;
+            updateLRUListLocked(r);
         }
         // If the activity was previously pausing, then ensure we transfer that as well
         if (setPause) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 27b8e91..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);
@@ -3555,8 +3569,16 @@
                 stackHeader.append("  mFullscreen=" + stack.mFullscreen);
                 stackHeader.append("\n");
                 stackHeader.append("  mBounds=" + stack.mBounds);
-                printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
-                        needSep, stackHeader.toString());
+
+                final boolean printedStackHeader = stack.dumpActivitiesLocked(fd, pw, dumpAll,
+                        dumpClient, dumpPackage, needSep, stackHeader.toString());
+                printed |= printedStackHeader;
+                if (!printedStackHeader) {
+                    // Ensure we always dump the stack header even if there are no activities
+                    pw.println();
+                    pw.println(stackHeader);
+                }
+
                 printed |= dumpHistoryList(fd, pw, stack.mLRUActivities, "    ", "Run", false,
                         !dumpAll, false, dumpPackage, true,
                         "    Running activities (most recent first):", null);
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 1b7b225..9258539 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -640,6 +640,18 @@
         final Intent ephemeralIntent = new Intent(intent);
         // Don't modify the client's object!
         intent = new Intent(intent);
+        if (componentSpecified
+                && intent.getData() != null
+                && Intent.ACTION_VIEW.equals(intent.getAction())
+                && intent.hasCategory(Intent.CATEGORY_BROWSABLE)
+                && mService.getPackageManagerInternalLocked()
+                        .isInstantAppInstallerComponent(intent.getComponent())) {
+            // intercept intents targeted directly to the ephemeral installer the
+            // ephemeral installer should never be started with a raw URL; instead
+            // adjust the intent so it looks like a "normal" instant app launch
+            intent.setComponent(null /*component*/);
+            componentSpecified = false;
+        }
 
         ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
         if (rInfo == null) {
@@ -1453,6 +1465,12 @@
         return intentActivity;
     }
 
+    /**
+     * Figure out which task and activity to bring to front when we have found an existing matching
+     * activity record in history. May also clear the task if needed.
+     * @param intentActivity Existing matching activity.
+     * @return {@link ActivityRecord} brought to front.
+     */
     private ActivityRecord setTargetStackAndMoveToFrontIfNeeded(ActivityRecord intentActivity) {
         mTargetStack = intentActivity.getStack();
         mTargetStack.mLastPausedActivity = null;
@@ -1514,6 +1532,14 @@
                                     "bringToFrontInsteadOfAdjacentLaunch");
                         }
                         mMovedToFront = true;
+                    } else if (launchStack.mDisplayId != mTargetStack.mDisplayId) {
+                        // Target and computed stacks are on different displays and we've
+                        // found a matching task - move the existing instance to that display and
+                        // move it to front.
+                        intentActivity.task.reparent(launchStack.mStackId, ON_TOP,
+                                REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+                                "reparentToDisplay");
+                        mMovedToFront = true;
                     }
                     mOptions = null;
 
@@ -1790,21 +1816,7 @@
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
 
-        if (mLaunchBounds != null) {
-            mInTask.updateOverrideConfiguration(mLaunchBounds);
-            int stackId = mInTask.getLaunchStackId();
-            if (stackId != mInTask.getStackId()) {
-                mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
-                        DEFER_RESUME, "inTaskToFront");
-                stackId = mInTask.getStackId();
-            }
-            if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
-            }
-        }
         mTargetStack = mInTask.getStack();
-        mTargetStack.moveTaskToFrontLocked(
-                mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
 
         // Check whether we should actually launch the new activity in to the task,
         // or just reuse the current activity on top.
@@ -1813,6 +1825,8 @@
                 && top.userId == mStartActivity.userId) {
             if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                     || mLaunchSingleTop || mLaunchSingleTask) {
+                mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+                        mStartActivity.appTimeTracker, "inTaskToFront");
                 ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.task);
                 if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
                     // We don't need to start a new activity, and the client said not to do
@@ -1826,12 +1840,31 @@
         }
 
         if (!mAddingToTask) {
+            mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+                    mStartActivity.appTimeTracker, "inTaskToFront");
             // We don't actually want to have this activity added to the task, so just
             // stop here but still tell the caller that we consumed the intent.
             ActivityOptions.abort(mOptions);
             return START_TASK_TO_FRONT;
         }
 
+        if (mLaunchBounds != null) {
+            mInTask.updateOverrideConfiguration(mLaunchBounds);
+            int stackId = mInTask.getLaunchStackId();
+            if (stackId != mInTask.getStackId()) {
+                mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+                        DEFER_RESUME, "inTaskToFront");
+                stackId = mInTask.getStackId();
+                mTargetStack = mInTask.getStack();
+            }
+            if (StackId.resizeStackWithLaunchBounds(stackId)) {
+                mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+            }
+        }
+
+        mTargetStack.moveTaskToFrontLocked(
+                mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
+
         addOrReparentStartingActivity(mInTask, "setTaskFromInTask");
         if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
                 + " in explicit task " + mStartActivity.task);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index f927cce..c10f77c 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -870,12 +870,22 @@
             nativeProcs = NATIVE_STACKS_OF_INTEREST;
         }
 
+        int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
+        ArrayList<Integer> nativePids = null;
+
+        if (pids != null) {
+            nativePids = new ArrayList<Integer>(pids.length);
+            for (int i : pids) {
+                nativePids.add(i);
+            }
+        }
+
         // For background ANRs, don't pass the ProcessCpuTracker to
         // avoid spending 1/2 second collecting stats to rank lastPids.
         File tracesFile = mService.dumpStackTraces(true, firstPids,
                                                    (isSilentANR) ? null : processCpuTracker,
                                                    (isSilentANR) ? null : lastPids,
-                                                   nativeProcs);
+                                                   nativePids);
 
         String cpuInfo = null;
         if (ActivityManagerService.MONITOR_CPU_USAGE) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index fd65c10..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;
@@ -617,15 +618,15 @@
         boolean kept = true;
         try {
             final ActivityRecord r = topRunningActivityLocked();
-            final boolean wasFocused = supervisor.isFocusedStack(sourceStack)
+            final boolean wasFocused = r != null && supervisor.isFocusedStack(sourceStack)
                     && (topRunningActivityLocked() == r);
-            final boolean wasResumed = sourceStack.mResumedActivity == r;
-            final boolean wasPaused = sourceStack.mPausingActivity == r;
+            final boolean wasResumed = r != null && sourceStack.mResumedActivity == r;
+            final boolean wasPaused = r != null && sourceStack.mPausingActivity == r;
 
             // In some cases the focused stack isn't the front stack. E.g. pinned stack.
             // Whenever we are moving the top activity from the front stack we want to make sure to
             // move the stack to the front.
-            final boolean wasFront = supervisor.isFrontStackOnDisplay(sourceStack)
+            final boolean wasFront = r != null && supervisor.isFrontStackOnDisplay(sourceStack)
                     && (sourceStack.topRunningActivityLocked() == r);
 
             // Adjust the position for the new parent stack as needed.
@@ -667,8 +668,10 @@
             // new stack by moving the stack to the front.
             final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
                     || (moveStackMode == REPARENT_KEEP_STACK_AT_FRONT && (wasFocused || wasFront));
-            toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed, wasPaused,
-                    reason);
+            if (r != null) {
+                toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed,
+                        wasPaused, reason);
+            }
             if (!animate) {
                 toStack.mNoAnimActivities.add(topActivity);
             }
@@ -1278,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
@@ -1289,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);
@@ -1302,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;
             }
@@ -1315,7 +1338,7 @@
      */
     final void performClearTaskLocked() {
         mReuseTask = true;
-        performClearTaskAtIndexLocked(0);
+        performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY);
         mReuseTask = false;
     }
 
@@ -1399,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 333d27b..49d1521 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -106,6 +106,7 @@
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
@@ -126,6 +127,7 @@
 import java.io.PrintWriter;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -575,6 +577,10 @@
     private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
     private long mLoweredFromNormalToVibrateTime;
 
+    // Array of Uids of valid accessibility services to check if caller is one of them
+    private int[] mAccessibilityServiceUids;
+    private final Object mAccessibilityServiceUidsLock = new Object();
+
     // Intent "extra" data keys.
     public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
     public static final String CONNECT_INTENT_KEY_STATE = "state";
@@ -1241,11 +1247,9 @@
     /** @see AudioManager#adjustStreamVolume(int, int, int) */
     public void adjustStreamVolume(int streamType, int direction, int flags,
             String callingPackage) {
-        if ( streamType == AudioManager.STREAM_ACCESSIBILITY
-                && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
-                        android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE))) {
+        if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
             Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without"
-                    + "BIND_ACCESSIBILITY_SERVICE / callingPackage=" + callingPackage);
+                    + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
             return;
         }
         adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
@@ -1559,17 +1563,33 @@
 
     /** @see AudioManager#setStreamVolume(int, int, int) */
     public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
-        if ( streamType == AudioManager.STREAM_ACCESSIBILITY
-                && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
-                        android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE))) {
+        if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
             Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
-                    + " BIND_ACCESSIBILITY_SERVICE  callingPackage=" + callingPackage);
+                    + " CHANGE_ACCESSIBILITY_VOLUME  callingPackage=" + callingPackage);
             return;
         }
         setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
                 Binder.getCallingUid());
     }
 
+    private boolean canChangeAccessibilityVolume() {
+        synchronized (mAccessibilityServiceUidsLock) {
+            if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.CHANGE_ACCESSIBILITY_VOLUME)) {
+                return true;
+            }
+            if (mAccessibilityServiceUids != null) {
+                int callingUid = Binder.getCallingUid();
+                for (int i = 0; i < mAccessibilityServiceUids.length; i++) {
+                    if (mAccessibilityServiceUids[i] == callingUid) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
     private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
             String caller, int uid) {
         if (DEBUG_VOL) {
@@ -6380,6 +6400,29 @@
                 }
             }
         }
+
+        @Override
+        public void setAccessibilityServiceUids(IntArray uids) {
+            synchronized (mAccessibilityServiceUidsLock) {
+                if (uids.size() == 0) {
+                    mAccessibilityServiceUids = null;
+                } else {
+                    boolean changed = (mAccessibilityServiceUids == null)
+                            || (mAccessibilityServiceUids.length != uids.size());
+                    if (!changed) {
+                        for (int i = 0; i < mAccessibilityServiceUids.length; i++) {
+                            if (uids.get(i) != mAccessibilityServiceUids[i]) {
+                                changed = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (changed) {
+                        mAccessibilityServiceUids = uids.toArray();
+                    }
+                }
+            }
+        }
     }
 
     //==========================================================================================
@@ -6555,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);
     }
@@ -6566,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/connectivity/MockableSystemProperties.java b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
index 4f68652..77b86d8 100644
--- a/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
+++ b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
@@ -19,7 +19,20 @@
 import android.os.SystemProperties;
 
 public class MockableSystemProperties {
+
+    public String get(String key) {
+        return SystemProperties.get(key);
+    }
+
+    public int getInt(String key, int def) {
+        return SystemProperties.getInt(key, def);
+    }
+
     public boolean getBoolean(String key, boolean def) {
         return SystemProperties.getBoolean(key, def);
     }
+
+    public void set(String key, String value) {
+        SystemProperties.set(key, value);
+    }
 }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index bbad493..1a27a39 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -907,7 +907,12 @@
                     Bundle finalExtras = new Bundle(extras);
                     String packageName = syncAdapterInfo.componentName.getPackageName();
                     // If the app did not run and has no account access, done
-                    if (!mPackageManagerInternal.wasPackageEverLaunched(packageName, userId)) {
+                    try {
+                        if (!mPackageManagerInternal.wasPackageEverLaunched(packageName, userId)) {
+                            continue;
+                        }
+                    } catch (IllegalArgumentException e) {
+                        // Package not found, race with an uninstall
                         continue;
                     }
                     mAccountManagerInternal.requestAccountAccess(account.account,
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..53a8092 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,
@@ -782,7 +772,12 @@
     private final class SessionStub extends ISession.Stub {
         @Override
         public void destroy() {
-            mService.destroySession(MediaSessionRecord.this);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mService.destroySession(MediaSessionRecord.this);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
@@ -817,6 +812,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 +843,19 @@
 
         @Override
         public void setPlaybackState(PlaybackState state) {
-            int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
-            int newState = state == null ? 0 : state.getState();
-            if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) {
-                mLastActiveTime = SystemClock.elapsedRealtime();
-            }
+            int oldState = mPlaybackState == null
+                    ? PlaybackState.STATE_NONE : mPlaybackState.getState();
+            int newState = state == null
+                    ? PlaybackState.STATE_NONE : state.getState();
             synchronized (mLock) {
                 mPlaybackState = state;
             }
-            mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mService.onSessionPlaystateChanged(MediaSessionRecord.this, oldState, newState);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
             mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index ea9128f..4bf9d8f 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -57,11 +57,13 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.speech.RecognizerIntent;
 import android.text.TextUtils;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -85,7 +87,7 @@
  */
 public class MediaSessionService extends SystemService implements Monitor {
     private static final String TAG = "MediaSessionService";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     // Leave log for key event always.
     private static final boolean DEBUG_KEY_EVENT = true;
 
@@ -114,6 +116,7 @@
     // It's always not null after the MediaSessionService is started.
     private FullUserRecord mCurrentFullUserRecord;
     private MediaSessionRecord mGlobalPrioritySession;
+    private AudioPlaybackMonitor mAudioPlaybackMonitor;
 
     // Used to notify system UI when remote volume was changed. TODO find a
     // better way to handle this.
@@ -134,6 +137,19 @@
         mKeyguardManager =
                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
         mAudioService = getAudioService();
+        mAudioPlaybackMonitor = new AudioPlaybackMonitor(getContext(), mAudioService,
+                new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
+                    @Override
+                    public void onAudioPlaybackStarted(int uid) {
+                        synchronized (mLock) {
+                            FullUserRecord user =
+                                    getFullUserRecordLocked(UserHandle.getUserId(uid));
+                            if (user != null) {
+                                user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+                            }
+                        }
+                    }
+                });
         mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
         mContentResolver = getContext().getContentResolver();
         mSettingsObserver = new SettingsObserver();
@@ -161,9 +177,10 @@
             user.mPriorityStack.onSessionStateChange(record);
             if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
                 mGlobalPrioritySession = record;
+                user.pushAddressedPlayerChangedLocked();
             }
+            mHandler.postSessionsChanged(record.getUserId());
         }
-        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
     }
 
     /**
@@ -180,18 +197,14 @@
         }
     }
 
-    public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
-        boolean updateSessions = false;
+    public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (user == null || !user.mPriorityStack.contains(record)) {
                 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
                 return;
             }
-            updateSessions = user.mPriorityStack.onPlaystateChange(record, oldState, newState);
-        }
-        if (updateSessions) {
-            mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
+            user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
         }
     }
 
@@ -332,6 +345,9 @@
         }
         if (mGlobalPrioritySession == session) {
             mGlobalPrioritySession = null;
+            if (session.isActive() && user != null) {
+                user.pushAddressedPlayerChangedLocked();
+            }
         }
 
         try {
@@ -340,8 +356,7 @@
             // ignore exceptions while destroying a session.
         }
         session.onDestroy();
-
-        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
+        mHandler.postSessionsChanged(session.getUserId());
     }
 
     private void enforcePackageName(String packageName, int uid) {
@@ -461,7 +476,7 @@
         }
 
         final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
-                callerPackageName, cb, tag, this, mHandler);
+                callerPackageName, cb, tag, this, mHandler.getLooper());
         try {
             cb.asBinder().linkToDeath(session, 0);
         } catch (RemoteException e) {
@@ -469,8 +484,7 @@
         }
 
         user.addSessionLocked(session);
-
-        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
+        mHandler.postSessionsChanged(userId);
 
         if (DEBUG) {
             Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
@@ -496,10 +510,6 @@
             }
             List<MediaSessionRecord> records = user.mPriorityStack.getActiveSessions(userId);
             int size = records.size();
-            if (size > 0 && records.get(0).isPlaybackActive(false)) {
-                user.rememberMediaButtonReceiverLocked(records.get(0));
-            }
-            user.pushAddressedPlayerChangedLocked();
             ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
             for (int i = 0; i < size; i++) {
                 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
@@ -536,6 +546,22 @@
         }
     }
 
+    /**
+     * Called when the media button receiver for the {@param record} is changed.
+     *
+     * @param record the media session whose media button receiver is updated.
+     */
+    public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+            MediaSessionRecord mediaButtonSession =
+                    user.mPriorityStack.getMediaButtonSession();
+            if (record == mediaButtonSession) {
+                user.rememberMediaButtonReceiverLocked(mediaButtonSession);
+            }
+        }
+    }
+
     private String getCallingPackageName(int uid) {
         String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
         if (packages != null && packages.length > 0) {
@@ -568,10 +594,10 @@
      * place makes more sense and increases the readability.</p>
      * <p>The contents of this object is guarded by {@link #mLock}.
      */
-    final class FullUserRecord {
+    final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
         private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
         private final int mFullUserId;
-        private final MediaSessionStack mPriorityStack = new MediaSessionStack();
+        private final MediaSessionStack mPriorityStack;
         private PendingIntent mLastMediaButtonReceiver;
         private ComponentName mRestoredMediaButtonReceiver;
         private int mRestoredMediaButtonReceiverUserId;
@@ -588,6 +614,7 @@
 
         public FullUserRecord(int fullUserId) {
             mFullUserId = fullUserId;
+            mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
             // Restore the remembered media button receiver before the boot.
             String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
                     Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
@@ -603,15 +630,14 @@
         }
 
         public void destroySessionsForUserLocked(int userId) {
-            List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, 0, userId);
+            List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
             for (MediaSessionRecord session : sessions) {
                 MediaSessionService.this.destroySessionLocked(session);
             }
         }
 
         public void addSessionLocked(MediaSessionRecord session) {
-            mPriorityStack.addSession(session,
-                    mFullUserId == mFullUserIds.get(session.getUserId()));
+            mPriorityStack.addSession(session);
         }
 
         public void removeSessionLocked(MediaSessionRecord session) {
@@ -642,21 +668,41 @@
             mPriorityStack.dump(pw, indent);
         }
 
-        // Remember the media button receiver and keep it in the persistent storage.
-        private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
+        @Override
+        public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
+                MediaSessionRecord newMediaButtonSession) {
+            if (DEBUG_KEY_EVENT) {
+                Log.d(TAG, "Media button session will be changed to " + newMediaButtonSession);
+            }
+            synchronized (mLock) {
+                if (oldMediaButtonSession != null) {
+                    mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
+                }
+                if (newMediaButtonSession != null) {
+                    rememberMediaButtonReceiverLocked(newMediaButtonSession);
+                    mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
+                }
+                pushAddressedPlayerChangedLocked();
+            }
+        }
+
+        // Remember media button receiver and keep it in the persistent storage.
+        public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
             PendingIntent receiver = record.getMediaButtonReceiver();
-            if (receiver == null) {
-                return;
-            }
             mLastMediaButtonReceiver = receiver;
-            ComponentName component = receiver.getIntent().getComponent();
-            if (component != null && record.getPackageName().equals(component.getPackageName())) {
-                String componentName = component.flattenToString();
-                Settings.Secure.putStringForUser(mContentResolver,
-                        Settings.System.MEDIA_BUTTON_RECEIVER,
-                        componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
-                        mFullUserId);
+            mRestoredMediaButtonReceiver = null;
+            String componentName = "";
+            if (receiver != null) {
+                ComponentName component = receiver.getIntent().getComponent();
+                if (component != null
+                        && record.getPackageName().equals(component.getPackageName())) {
+                    componentName = component.flattenToString();
+                }
             }
+            Settings.Secure.putStringForUser(mContentResolver,
+                    Settings.System.MEDIA_BUTTON_RECEIVER,
+                    componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
+                    mFullUserId);
         }
 
         private void pushAddressedPlayerChangedLocked() {
@@ -682,14 +728,8 @@
         }
 
         private MediaSessionRecord getMediaButtonSessionLocked() {
-            if (isGlobalPriorityActiveLocked()) {
-                return mGlobalPrioritySession;
-            }
-            // If we don't have a media button receiver to fall back on
-            // include non-playing sessions for dispatching.
-            boolean useNotPlayingSessions = (mLastMediaButtonReceiver == null
-                    && mRestoredMediaButtonReceiver == null);
-            return mPriorityStack.getDefaultMediaButtonSession(useNotPlayingSessions);
+            return isGlobalPriorityActiveLocked()
+                    ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
         }
     }
 
@@ -1262,6 +1302,7 @@
                 for (int i = 0; i < count; i++) {
                     mUserRecords.valueAt(i).dumpLocked(pw, "");
                 }
+                mAudioPlaybackMonitor.dump(pw, "");
             }
         }
 
@@ -1390,7 +1431,7 @@
                         PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
                         if (DEBUG_KEY_EVENT) {
                             Log.d(TAG, "Sending " + keyEvent
-                                    + " to the last known pendingIntent " + receiver);
+                                    + " to the last known PendingIntent " + receiver);
                         }
                         receiver.send(getContext(),
                                 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
@@ -1413,7 +1454,8 @@
                         }
                         mediaButtonIntent.setComponent(receiver);
                         getContext().sendBroadcastAsUser(mediaButtonIntent,
-                                UserHandle.of(mCurrentFullUserRecord.mRestoredMediaButtonReceiverUserId));
+                                UserHandle.of(mCurrentFullUserRecord
+                                      .mRestoredMediaButtonReceiverUserId));
                         if (mCurrentFullUserRecord.mCallback != null) {
                             mCurrentFullUserRecord.mCallback
                                     .onMediaKeyEventDispatchedToMediaButtonReceiver(
@@ -1426,23 +1468,6 @@
                 } catch (RemoteException e) {
                     Log.w(TAG, "Failed to send callback", e);
                 }
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG, "Sending media key ordered broadcast");
-                }
-                if (needWakeLock) {
-                    mMediaEventWakeLock.acquire();
-                }
-                // Fallback to legacy behavior
-                Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
-                keyIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-                if (needWakeLock) {
-                    keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
-                }
-                // Send broadcast only to the full user.
-                getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.CURRENT,
-                        null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
             }
         }
 
@@ -1637,12 +1662,13 @@
     final class MessageHandler extends Handler {
         private static final int MSG_SESSIONS_CHANGED = 1;
         private static final int MSG_VOLUME_INITIAL_DOWN = 2;
+        private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SESSIONS_CHANGED:
-                    pushSessionsChanged(msg.arg1);
+                    pushSessionsChanged((int) msg.obj);
                     break;
                 case MSG_VOLUME_INITIAL_DOWN:
                     synchronized (mLock) {
@@ -1657,8 +1683,15 @@
             }
         }
 
-        public void post(int what, int arg1, int arg2) {
-            obtainMessage(what, arg1, arg2).sendToTarget();
+        public void postSessionsChanged(int userId) {
+            // Use object instead of the arguments when posting message to remove pending requests.
+            Integer userIdInteger = mIntegerCache.get(userId);
+            if (userIdInteger == null) {
+                userIdInteger = Integer.valueOf(userId);
+                mIntegerCache.put(userId, userIdInteger);
+            }
+            removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
+            obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
         }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 8b80734..b0d8adc 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -16,12 +16,13 @@
 
 package com.android.server.media;
 
-import android.app.ActivityManager;
 import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.PlaybackState;
 import android.media.session.MediaSession;
-import android.os.RemoteException;
+import android.media.session.PlaybackState;
+import android.os.Debug;
 import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -33,6 +34,20 @@
  * <p>This class isn't thread-safe. The caller should take care of the synchronization.
  */
 class MediaSessionStack {
+    private static final boolean DEBUG = MediaSessionService.DEBUG;
+    private static final String TAG = "MediaSessionStack";
+
+    /**
+     * Listens the change in the media button session.
+     */
+    interface OnMediaButtonSessionChangedListener {
+        /**
+         * Called when the media button session is changed.
+         */
+        void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
+                MediaSessionRecord newMediaButtonSession);
+    }
+
     /**
      * These are states that usually indicate the user took an action and should
      * bump priority regardless of the old state.
@@ -51,57 +66,45 @@
             PlaybackState.STATE_CONNECTING,
             PlaybackState.STATE_PLAYING };
 
-    private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+    /**
+     * Sorted list of the media sessions.
+     * The session of which PlaybackState is changed to ALWAYS_PRIORITY_STATES or
+     * TRANSITION_PRIORITY_STATES comes first.
+     * @see #shouldUpdatePriority
+     */
+    private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
 
-    // The last record that either entered one of the playing states or was
-    // added.
-    private MediaSessionRecord mLastInterestingRecord;
-    private MediaSessionRecord mCachedButtonReceiver;
+    private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
+
+    /**
+     * The media button session which receives media key events.
+     * It could be null if the previous media buttion session is released.
+     */
+    private MediaSessionRecord mMediaButtonSession;
+
     private MediaSessionRecord mCachedDefault;
     private MediaSessionRecord mCachedVolumeDefault;
     private ArrayList<MediaSessionRecord> mCachedActiveList;
-    private ArrayList<MediaSessionRecord> mCachedTransportControlList;
 
-    /**
-     * Checks if a media session is created from the most recent app.
-     *
-     * @param record A media session record to be examined.
-     * @return {@code true} if the media session's package name equals to the most recent app, false
-     *            otherwise.
-     */
-    private static boolean isFromMostRecentApp(MediaSessionRecord record) {
-        try {
-            List<ActivityManager.RecentTaskInfo> tasks =
-                    ActivityManager.getService().getRecentTasks(1,
-                            ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS |
-                            ActivityManager.RECENT_IGNORE_UNAVAILABLE |
-                            ActivityManager.RECENT_INCLUDE_PROFILES |
-                            ActivityManager.RECENT_WITH_EXCLUDED, record.getUserId()).getList();
-            if (tasks != null && !tasks.isEmpty()) {
-                ActivityManager.RecentTaskInfo recentTask = tasks.get(0);
-                if (recentTask.userId == record.getUserId() && recentTask.baseIntent != null) {
-                    return recentTask.baseIntent.getComponent().getPackageName()
-                            .equals(record.getPackageName());
-                }
-            }
-        } catch (RemoteException e) {
-            return false;
-        }
-        return false;
+    MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) {
+        mAudioPlaybackMonitor = monitor;
+        mOnMediaButtonSessionChangedListener = listener;
     }
 
     /**
      * Add a record to the priority tracker.
      *
      * @param record The record to add.
-     * @param fromForegroundUser {@code true} if the session is created by the foreground user.
      */
-    public void addSession(MediaSessionRecord record, boolean fromForegroundUser) {
+    public void addSession(MediaSessionRecord record) {
         mSessions.add(record);
         clearCache();
-        if (fromForegroundUser && isFromMostRecentApp(record)) {
-            mLastInterestingRecord = record;
-        }
+
+        // Update the media button session.
+        // The added session could be the session from the package with the audio playback.
+        // This can happen if an app starts audio playback before creating media session.
+        updateMediaButtonSessionIfNeeded();
     }
 
     /**
@@ -111,6 +114,11 @@
      */
     public void removeSession(MediaSessionRecord record) {
         mSessions.remove(record);
+        if (mMediaButtonSession == record) {
+            // When the media button session is gone, try to find the alternative media session
+            // in the media button session app.
+            onMediaSessionChangeInMediaButtonSessionApp();
+        }
         clearCache();
     }
 
@@ -122,32 +130,33 @@
     }
 
     /**
-     * Notify the priority tracker that a session's state changed.
+     * Notify the priority tracker that a session's playback state changed.
      *
      * @param record The record that changed.
      * @param oldState Its old playback state.
      * @param newState Its new playback state.
-     * @return true if the priority order was updated, false otherwise.
      */
-    public boolean onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
+    public void onPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
         if (shouldUpdatePriority(oldState, newState)) {
             mSessions.remove(record);
             mSessions.add(0, record);
             clearCache();
-            // This becomes the last interesting record since it entered a
-            // playing state
-            mLastInterestingRecord = record;
-            return true;
         } else if (!MediaSession.isActiveState(newState)) {
             // Just clear the volume cache when a state goes inactive
             mCachedVolumeDefault = null;
         }
-        return false;
+
+        // In most cases, playback state isn't needed for finding media buttion session,
+        // but we only use it as a hint if an app has multiple local media sessions.
+        // In that case, we pick the media session whose PlaybackState matches
+        // the audio playback configuration.
+        if (mMediaButtonSession != null && mMediaButtonSession.getUid() == record.getUid()) {
+            onMediaSessionChangeInMediaButtonSessionApp();
+        }
     }
 
     /**
-     * Handle any stack changes that need to occur in response to a session
-     * state change. TODO add the old and new session state as params
+     * Handle the change in activeness for a session.
      *
      * @param record The record that changed.
      */
@@ -158,6 +167,81 @@
     }
 
     /**
+     * Update the media button session if needed.
+     * <p>The media button session is the session that will receive the media button events.
+     * <p>We send the media button events to the lastly played app. If the app has the media
+     * session, the session will receive the media button events.
+     */
+    public void updateMediaButtonSessionIfNeeded() {
+        if (DEBUG) {
+            Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
+        }
+        IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+        for (int i = 0; i < audioPlaybackUids.size(); i++) {
+            MediaSessionRecord mediaButtonSession =
+                    findMediaButtonSession(audioPlaybackUids.get(i));
+            if (mediaButtonSession != null) {
+                // Found the media button session.
+                mAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
+                if (mMediaButtonSession != mediaButtonSession) {
+                    mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(
+                            mMediaButtonSession, mediaButtonSession);
+                    mMediaButtonSession = mediaButtonSession;
+                }
+                return;
+            }
+        }
+    }
+
+    /**
+     * Handle the change in a media session in the media button session app.
+     * <p>If the app has multiple media sessions, change in a media sesion in the app may change
+     * the media button session.
+     * @see #findMediaButtonSession
+     */
+    private void onMediaSessionChangeInMediaButtonSessionApp() {
+        MediaSessionRecord newMediaButtonSession =
+                findMediaButtonSession(mMediaButtonSession.getUid());
+        if (newMediaButtonSession != mMediaButtonSession) {
+            mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(mMediaButtonSession,
+                    newMediaButtonSession);
+            mMediaButtonSession = newMediaButtonSession;
+        }
+    }
+
+    /**
+     * Find the media button session with the given {@param uid}.
+     * If the app has multiple media sessions, the media session matches the audio playback state
+     * becomes the media button session.
+     *
+     * @return The media button session. Returns {@code null} if the app doesn't have a media
+     *   session.
+     */
+    private MediaSessionRecord findMediaButtonSession(int uid) {
+        MediaSessionRecord mediaButtonSession = null;
+        for (MediaSessionRecord session : mSessions) {
+            // Since the media buttons come with the headset/speaker, users will expect changes in
+            // the headset/speaker when they press media buttons. So only consider local playback
+            // for the media button session.
+            if (uid == session.getUid()
+                    && session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+                if (session.isPlaybackActive() ==
+                        mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
+                    // If there's a media session whose PlaybackState matches
+                    // the audio playback state, return it immediately.
+                    return session;
+                }
+                if (mediaButtonSession == null) {
+                    // Among the media sessions whose PlaybackState doesn't match
+                    // the audio playback state, pick the top priority.
+                    mediaButtonSession = session;
+                }
+            }
+        }
+        return mediaButtonSession;
+    }
+
+    /**
      * Get the current priority sorted list of active sessions. The most
      * important session is at index 0 and the least important at size - 1.
      *
@@ -166,57 +250,29 @@
      */
     public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
         if (mCachedActiveList == null) {
-            mCachedActiveList = getPriorityList(true, 0, userId);
+            mCachedActiveList = getPriorityList(true, userId);
         }
         return mCachedActiveList;
     }
 
     /**
-     * Get the highest priority session that can handle media buttons.
+     * Get the media button session which receives the media button events.
      *
-     * @param includeNotPlaying Return a non-playing session if nothing else is
-     *            available
-     * @return The default media button session or null.
+     * @return The media button session or null.
      */
-    public MediaSessionRecord getDefaultMediaButtonSession(boolean includeNotPlaying) {
-        if (mCachedButtonReceiver != null) {
-            return mCachedButtonReceiver;
-        }
-        ArrayList<MediaSessionRecord> records = getPriorityList(true,
-                MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, UserHandle.USER_ALL);
-        if (records.size() > 0) {
-            MediaSessionRecord record = records.get(0);
-            if (record.isPlaybackActive(false)) {
-                // Since we're going to send a button event to this record make
-                // it the last interesting one.
-                mLastInterestingRecord = record;
-                mCachedButtonReceiver = record;
-            } else if (mLastInterestingRecord != null) {
-                if (records.contains(mLastInterestingRecord)) {
-                    mCachedButtonReceiver = mLastInterestingRecord;
-                } else {
-                    // That record is no longer used. Clear its reference.
-                    mLastInterestingRecord = null;
-                }
-            }
-            if (includeNotPlaying && mCachedButtonReceiver == null) {
-                // If we really want a record and we didn't find one yet use the
-                // highest priority session even if it's not playing.
-                mCachedButtonReceiver = record;
-            }
-        }
-        return mCachedButtonReceiver;
+    public MediaSessionRecord getMediaButtonSession() {
+        return mMediaButtonSession;
     }
 
     public MediaSessionRecord getDefaultVolumeSession() {
         if (mCachedVolumeDefault != null) {
             return mCachedVolumeDefault;
         }
-        ArrayList<MediaSessionRecord> records = getPriorityList(true, 0, UserHandle.USER_ALL);
+        ArrayList<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
         int size = records.size();
         for (int i = 0; i < size; i++) {
             MediaSessionRecord record = records.get(i);
-            if (record.isPlaybackActive(false)) {
+            if (record.isPlaybackActive()) {
                 mCachedVolumeDefault = record;
                 return record;
             }
@@ -225,7 +281,7 @@
     }
 
     public MediaSessionRecord getDefaultRemoteSession(int userId) {
-        ArrayList<MediaSessionRecord> records = getPriorityList(true, 0, userId);
+        ArrayList<MediaSessionRecord> records = getPriorityList(true, userId);
 
         int size = records.size();
         for (int i = 0; i < size; i++) {
@@ -238,9 +294,10 @@
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false, 0,
+        ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false,
                 UserHandle.USER_ALL);
         int count = sortedSessions.size();
+        pw.println(prefix + "Media button session is " + mMediaButtonSession);
         pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
         String indent = prefix + "  ";
         for (int i = 0; i < count; i++) {
@@ -252,22 +309,23 @@
 
     /**
      * Get a priority sorted list of sessions. Can filter to only return active
-     * sessions or sessions with specific flags.
+     * sessions or sessions.
+     * <p>Here's the priority order.
+     * <li>System priority session (session with FLAG_EXCLUSIVE_GLOBAL_PRIORITY)</li>
+     * <li>Active sessions whose PlaybackState is active</li>
+     * <li>Active sessions whose PlaybackState is inactive</li>
+     * <li>Inactive sessions</li>
      *
      * @param activeOnly True to only return active sessions, false to return
      *            all sessions.
-     * @param withFlags Only return sessions with all the specified flags set. 0
-     *            returns all sessions.
      * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
      *            will return sessions for all users.
      * @return The priority sorted list of sessions.
      */
-    public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int withFlags,
-            int userId) {
+    public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
         ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
-        int lastLocalIndex = 0;
+        int lastPlaybackActiveIndex = 0;
         int lastActiveIndex = 0;
-        int lastPublishedIndex = 0;
 
         int size = mSessions.size();
         for (int i = 0; i < size; i++) {
@@ -277,10 +335,7 @@
                 // Filter out sessions for the wrong user
                 continue;
             }
-            if ((session.getFlags() & withFlags) != withFlags) {
-                // Filter out sessions with the wrong flags
-                continue;
-            }
+
             if (!session.isActive()) {
                 if (!activeOnly) {
                     // If we're getting unpublished as well always put them at
@@ -294,28 +349,13 @@
                 // System priority sessions are special and always go at the
                 // front. We expect there to only be one of these at a time.
                 result.add(0, session);
-                lastLocalIndex++;
+                lastPlaybackActiveIndex++;
                 lastActiveIndex++;
-                lastPublishedIndex++;
-            } else if (session.isPlaybackActive(true)) {
-                // TODO this with real local route check
-                if (true) {
-                    // Active local sessions get top priority
-                    result.add(lastLocalIndex, session);
-                    lastLocalIndex++;
-                    lastActiveIndex++;
-                    lastPublishedIndex++;
-                } else {
-                    // Then active remote sessions
-                    result.add(lastActiveIndex, session);
-                    lastActiveIndex++;
-                    lastPublishedIndex++;
-                }
+            } else if (session.isPlaybackActive()) {
+                result.add(lastPlaybackActiveIndex++, session);
+                lastActiveIndex++;
             } else {
-                // inactive sessions go at the end in order of whoever last did
-                // something.
-                result.add(lastPublishedIndex, session);
-                lastPublishedIndex++;
+                result.add(lastActiveIndex++, session);
             }
         }
 
@@ -345,8 +385,6 @@
     private void clearCache() {
         mCachedDefault = null;
         mCachedVolumeDefault = null;
-        mCachedButtonReceiver = null;
         mCachedActiveList = null;
-        mCachedTransportControlList = null;
     }
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index fc45344..b165d34 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -75,6 +75,9 @@
 import static android.net.wifi.WifiManager.EXTRA_NETWORK_INFO;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_CONFIGURATION;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_INFO;
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
+import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
+import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 
 import static com.android.internal.util.ArrayUtils.appendInt;
@@ -141,6 +144,7 @@
 import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.MessageQueue.IdleHandler;
+import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.Process;
@@ -153,6 +157,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -174,12 +179,14 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.DeviceIdleController;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
 
 import com.android.server.power.BatterySaverPolicy.ServiceType;
@@ -204,6 +211,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Calendar;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -310,6 +318,9 @@
     private static final int MSG_SET_FIREWALL_RULES = 14;
     private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
 
+    private static final int UID_MSG_STATE_CHANGED = 100;
+    private static final int UID_MSG_GONE = 101;
+
     private final Context mContext;
     private final IActivityManager mActivityManager;
     private final INetworkStatsService mNetworkStats;
@@ -317,6 +328,7 @@
     private UsageStatsManagerInternal mUsageStats;
     private final TrustedTime mTime;
     private final UserManager mUserManager;
+    private final CarrierConfigManager mCarrierConfigManager;
 
     private IConnectivityManager mConnManager;
     private INotificationManager mNotifManager;
@@ -420,6 +432,9 @@
             mListeners = new RemoteCallbackList<>();
 
     final Handler mHandler;
+    final Handler mUidEventHandler;
+
+    private final ServiceThread mUidEventThread;
 
     @GuardedBy("allLocks")
     private final AtomicFile mPolicyFile;
@@ -465,12 +480,19 @@
                 Context.DEVICE_IDLE_CONTROLLER));
         mTime = checkNotNull(time, "missing TrustedTime");
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
         mIPm = pm;
 
         HandlerThread thread = new HandlerThread(TAG);
         thread.start();
         mHandler = new Handler(thread.getLooper(), mHandlerCallback);
 
+        // We create another thread for the UID events, which are more time-critical.
+        mUidEventThread = new ServiceThread(TAG + ".uid", Process.THREAD_PRIORITY_FOREGROUND,
+                /*allowIo=*/ false);
+        mUidEventThread.start();
+        mUidEventHandler = new Handler(mUidEventThread.getLooper(), mUidEventHandlerCallback);
+
         mSuppressDefaultPolicy = suppressDefaultPolicy;
 
         mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"));
@@ -743,6 +765,11 @@
                     WifiManager.NETWORK_STATE_CHANGED_ACTION);
             mContext.registerReceiver(mWifiStateReceiver, wifiStateFilter, null, mHandler);
 
+            // listen for carrier config changes to update data cycle information
+            final IntentFilter carrierConfigFilter = new IntentFilter(
+                    ACTION_CARRIER_CONFIG_CHANGED);
+            mContext.registerReceiver(mCarrierConfigReceiver, carrierConfigFilter, null, mHandler);
+
             mUsageStats.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
             // tell systemReady() that the service has been initialized
             initCompleteSignal.countDown();
@@ -774,26 +801,12 @@
     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override public void onUidStateChanged(int uid, int procState,
                 long procStateSeq) throws RemoteException {
-            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
-            try {
-                synchronized (mUidRulesFirstLock) {
-                    // We received a uid state change callback, add it to the history so that it
-                    // will be useful for debugging.
-                    mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
-                    // Now update the network policy rules as per the updated uid state.
-                    updateUidStateUL(uid, procState);
-                    // Updating the network rules is done, so notify AMS about this.
-                    mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
-                }
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
-            }
+            mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED,
+                    uid, procState, procStateSeq).sendToTarget();
         }
 
         @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
-            synchronized (mUidRulesFirstLock) {
-                removeUidStateUL(uid);
-            }
+            mUidEventHandler.obtainMessage(UID_MSG_GONE, uid, 0).sendToTarget();
         }
 
         @Override public void onUidActive(int uid) throws RemoteException {
@@ -1293,6 +1306,213 @@
     };
 
     /**
+     * Update mobile policies with data cycle information from {@link CarrierConfigManager}
+     * if necessary.
+     *
+     * @param subId that has its associated NetworkPolicy updated if necessary
+     * @return if any policies were updated
+     */
+    private boolean maybeUpdateMobilePolicyCycleNL(int subId) {
+        if (LOGV) Slog.v(TAG, "maybeUpdateMobilePolicyCycleNL()");
+        final PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+
+        if (config == null) {
+            return false;
+        }
+
+        boolean policyUpdated = false;
+        final String subscriberId = TelephonyManager.from(mContext).getSubscriberId(subId);
+
+        // find and update the mobile NetworkPolicy for this subscriber id
+        final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true);
+        for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
+            final NetworkTemplate template = mNetworkPolicy.keyAt(i);
+            if (template.matches(probeIdent)) {
+                NetworkPolicy policy = mNetworkPolicy.valueAt(i);
+
+                // only update the policy if the user didn't change any of the defaults.
+                if (!policy.inferred) {
+                    // TODO: inferred could be split, so that if a user changes their data limit or
+                    // warning, it doesn't prevent their cycle date from being updated.
+                    if (LOGD) Slog.v(TAG, "Didn't update NetworkPolicy because policy.inferred");
+                    continue;
+                }
+
+                final int cycleDay = getCycleDayFromCarrierConfig(config, policy.cycleDay);
+                final long warningBytes = getWarningBytesFromCarrierConfig(config,
+                        policy.warningBytes);
+                final long limitBytes = getLimitBytesFromCarrierConfig(config,
+                        policy.limitBytes);
+
+                if (policy.cycleDay == cycleDay &&
+                        policy.warningBytes == warningBytes &&
+                        policy.limitBytes == limitBytes) {
+                    continue;
+                }
+
+                policyUpdated = true;
+                policy.cycleDay = cycleDay;
+                policy.warningBytes = warningBytes;
+                policy.limitBytes = limitBytes;
+
+                if (LOGD) {
+                    Slog.d(TAG, "Updated NetworkPolicy " + policy + " which matches subscriber "
+                            + NetworkIdentity.scrubSubscriberId(subscriberId)
+                            + " from CarrierConfigManager");
+                }
+            }
+        }
+
+        return policyUpdated;
+    }
+
+    /**
+     * Returns the cycle day that should be used for a mobile NetworkPolicy.
+     *
+     * It attempts to get an appropriate cycle day from the passed in CarrierConfig. If it's unable
+     * to do so, it returns the fallback value.
+     *
+     * @param config The CarrierConfig to read the value from.
+     * @param fallbackCycleDay to return if the CarrierConfig can't be read.
+     * @return cycleDay to use in the mobile NetworkPolicy.
+     */
+    @VisibleForTesting
+    public int getCycleDayFromCarrierConfig(@Nullable PersistableBundle config,
+            int fallbackCycleDay) {
+        if (config == null) {
+            return fallbackCycleDay;
+        }
+        int cycleDay =
+                config.getInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT);
+        if (cycleDay == DATA_CYCLE_USE_PLATFORM_DEFAULT) {
+            return fallbackCycleDay;
+        }
+        // validate cycleDay value
+        final Calendar cal = Calendar.getInstance();
+        if (cycleDay < cal.getMinimum(Calendar.DAY_OF_MONTH) ||
+                cycleDay > cal.getMaximum(Calendar.DAY_OF_MONTH)) {
+            Slog.e(TAG, "Invalid date in "
+                    + "CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT: " + cycleDay);
+            return fallbackCycleDay;
+        }
+        return cycleDay;
+    }
+
+    /**
+     * Returns the warning bytes that should be used for a mobile NetworkPolicy.
+     *
+     * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable
+     * to do so, it returns the fallback value.
+     *
+     * @param config The CarrierConfig to read the value from.
+     * @param fallbackWarningBytes to return if the CarrierConfig can't be read.
+     * @return warningBytes to use in the mobile NetworkPolicy.
+     */
+    @VisibleForTesting
+    public long getWarningBytesFromCarrierConfig(@Nullable PersistableBundle config,
+            long fallbackWarningBytes) {
+        if (config == null) {
+            return fallbackWarningBytes;
+        }
+        long warningBytes =
+                config.getLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG);
+
+        if (warningBytes == DATA_CYCLE_THRESHOLD_DISABLED) {
+            return WARNING_DISABLED;
+        } else if (warningBytes == DATA_CYCLE_USE_PLATFORM_DEFAULT) {
+            return getPlatformDefaultWarningBytes();
+        } else if (warningBytes < 0) {
+            Slog.e(TAG, "Invalid value in "
+                    + "CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG; expected a "
+                    + "non-negative value but got: " + warningBytes);
+            return fallbackWarningBytes;
+        }
+
+        return warningBytes;
+    }
+
+    /**
+     * Returns the limit bytes that should be used for a mobile NetworkPolicy.
+     *
+     * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable
+     * to do so, it returns the fallback value.
+     *
+     * @param config The CarrierConfig to read the value from.
+     * @param fallbackLimitBytes to return if the CarrierConfig can't be read.
+     * @return limitBytes to use in the mobile NetworkPolicy.
+     */
+    @VisibleForTesting
+    public long getLimitBytesFromCarrierConfig(@Nullable PersistableBundle config,
+            long fallbackLimitBytes) {
+        if (config == null) {
+            return fallbackLimitBytes;
+        }
+        long limitBytes =
+                config.getLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG);
+
+        if (limitBytes == DATA_CYCLE_THRESHOLD_DISABLED) {
+            return LIMIT_DISABLED;
+        } else if (limitBytes == DATA_CYCLE_USE_PLATFORM_DEFAULT) {
+            return getPlatformDefaultLimitBytes();
+        } else if (limitBytes < 0) {
+            Slog.e(TAG, "Invalid value in "
+                    + "CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG; expected a "
+                    + "non-negative value but got: " + limitBytes);
+            return fallbackLimitBytes;
+        }
+        return limitBytes;
+    }
+
+    /**
+     * Receiver that watches for {@link CarrierConfigManager} to be changed.
+     */
+    private BroadcastReceiver mCarrierConfigReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // No need to do a permission check, because the ACTION_CARRIER_CONFIG_CHANGED
+            // broadcast is protected and can't be spoofed. Runs on a background handler thread.
+
+            if (!intent.hasExtra(PhoneConstants.SUBSCRIPTION_KEY)) {
+                return;
+            }
+            final int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, -1);
+            final TelephonyManager tele = TelephonyManager.from(mContext);
+            final String subscriberId = tele.getSubscriberId(subId);
+
+            maybeRefreshTrustedTime();
+            synchronized (mUidRulesFirstLock) {
+                synchronized (mNetworkPoliciesSecondLock) {
+                    final boolean added = ensureActiveMobilePolicyNL(subId, subscriberId);
+                    if (added) return;
+                    final boolean updated = maybeUpdateMobilePolicyCycleNL(subId);
+                    if (!updated) return;
+                    // update network and notification rules, as the data cycle changed and it's
+                    // possible that we should be triggering warnings/limits now
+                    handleNetworkPoliciesUpdateAL(true);
+                }
+            }
+        }
+    };
+
+    /**
+     * Handles all tasks that need to be run after a new network policy has been set, or an existing
+     * one has been updated.
+     *
+     * @param shouldNormalizePolicies true iff network policies need to be normalized after the
+     *                                update.
+     */
+    void handleNetworkPoliciesUpdateAL(boolean shouldNormalizePolicies) {
+        if (shouldNormalizePolicies) {
+            normalizePoliciesNL();
+        }
+        updateNetworkEnabledNL();
+        updateNetworkRulesNL();
+        updateNotificationsNL();
+        writePolicyAL();
+    }
+
+    /**
      * Proactively control network data connections when they exceed
      * {@link NetworkPolicy#limitBytes}.
      */
@@ -1517,11 +1737,19 @@
         final int[] subIds = sub.getActiveSubscriptionIdList();
         for (int subId : subIds) {
             final String subscriberId = tele.getSubscriberId(subId);
-            ensureActiveMobilePolicyNL(subscriberId);
+            ensureActiveMobilePolicyNL(subId, subscriberId);
         }
     }
 
-    private void ensureActiveMobilePolicyNL(String subscriberId) {
+    /**
+     * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we
+     * have at least a default mobile policy defined.
+     *
+     * @param subId to build a default policy for
+     * @param subscriberId that we check for an existing policy
+     * @return true if a mobile network policy was added, or false one already existed.
+     */
+    private boolean ensureActiveMobilePolicyNL(int subId, String subscriberId) {
         // Poke around to see if we already have a policy
         final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
                 TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true);
@@ -1532,33 +1760,51 @@
                     Slog.d(TAG, "Found template " + template + " which matches subscriber "
                             + NetworkIdentity.scrubSubscriberId(subscriberId));
                 }
-                return;
+                return false;
             }
         }
 
         Slog.i(TAG, "No policy for subscriber " + NetworkIdentity.scrubSubscriberId(subscriberId)
                 + "; generating default policy");
+        final NetworkPolicy policy = buildDefaultMobilePolicy(subId, subscriberId);
+        addNetworkPolicyNL(policy);
+        return true;
+    }
 
-        // Build default mobile policy, and assume usage cycle starts today
+    private long getPlatformDefaultWarningBytes() {
         final int dataWarningConfig = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_networkPolicyDefaultWarning);
-        final long warningBytes;
         if (dataWarningConfig == WARNING_DISABLED) {
-            warningBytes = WARNING_DISABLED;
+            return WARNING_DISABLED;
         } else {
-            warningBytes = dataWarningConfig * MB_IN_BYTES;
+            return dataWarningConfig * MB_IN_BYTES;
         }
+    }
 
+    private long getPlatformDefaultLimitBytes() {
+        return LIMIT_DISABLED;
+    }
+
+    @VisibleForTesting
+    public NetworkPolicy buildDefaultMobilePolicy(int subId, String subscriberId) {
+        PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+
+        // assume usage cycle starts today
         final Time time = new Time();
         time.setToNow();
 
-        final int cycleDay = time.monthDay;
         final String cycleTimezone = time.timezone;
 
+        final int cycleDay = getCycleDayFromCarrierConfig(config, time.monthDay);
+        final long warningBytes = getWarningBytesFromCarrierConfig(config,
+                getPlatformDefaultWarningBytes());
+        final long limitBytes = getLimitBytesFromCarrierConfig(config,
+                getPlatformDefaultLimitBytes());
+
         final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
         final NetworkPolicy policy = new NetworkPolicy(template, cycleDay, cycleTimezone,
-                warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true, true);
-        addNetworkPolicyNL(policy);
+                warningBytes, limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, true);
+        return policy;
     }
 
     private void readPolicyAL() {
@@ -2026,10 +2272,7 @@
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
                     normalizePoliciesNL(policies);
-                    updateNetworkEnabledNL();
-                    updateNetworkRulesNL();
-                    updateNotificationsNL();
-                    writePolicyAL();
+                    handleNetworkPoliciesUpdateAL(false);
                 }
             }
         } finally {
@@ -2126,11 +2369,7 @@
                         throw new IllegalArgumentException("unexpected type");
                 }
 
-                normalizePoliciesNL();
-                updateNetworkEnabledNL();
-                updateNetworkRulesNL();
-                updateNotificationsNL();
-                writePolicyAL();
+                handleNetworkPoliciesUpdateAL(true);
             }
         }
     }
@@ -2361,11 +2600,7 @@
                         mNetworkPolicy.valueAt(i).clearSnooze();
                     }
 
-                    normalizePoliciesNL();
-                    updateNetworkEnabledNL();
-                    updateNetworkRulesNL();
-                    updateNotificationsNL();
-                    writePolicyAL();
+                    handleNetworkPoliciesUpdateAL(true);
 
                     fout.println("Cleared snooze timestamps");
                     return;
@@ -3317,7 +3552,7 @@
         }
     }
 
-    private Handler.Callback mHandlerCallback = new Handler.Callback() {
+    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
         @Override
         public boolean handleMessage(Message msg) {
             switch (msg.what) {
@@ -3441,9 +3676,61 @@
                 }
             }
         }
+    };
+
+    private final Handler.Callback mUidEventHandlerCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case UID_MSG_STATE_CHANGED: {
+                    final int uid = msg.arg1;
+                    final int procState = msg.arg2;
+                    final long procStateSeq = (Long) msg.obj;
+
+                    handleUidChanged(uid, procState, procStateSeq);
+                    return true;
+                }
+                case UID_MSG_GONE: {
+                    final int uid = msg.arg1;
+                    handleUidGone(uid);
+                    return true;
+                }
+                default: {
+                    return false;
+                }
+            }
+        }
 
     };
 
+    void handleUidChanged(int uid, int procState, long procStateSeq) {
+        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
+        try {
+            synchronized (mUidRulesFirstLock) {
+                // We received a uid state change callback, add it to the history so that it
+                // will be useful for debugging.
+                mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+                // Now update the network policy rules as per the updated uid state.
+                updateUidStateUL(uid, procState);
+                // Updating the network rules is done, so notify AMS about this.
+                mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+        }
+    }
+
+    void handleUidGone(int uid) {
+        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidGone");
+        try {
+            synchronized (mUidRulesFirstLock) {
+                removeUidStateUL(uid);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+        }
+    }
+
     private void broadcastRestrictBackgroundChanged(int uid, Boolean changed) {
         final PackageManager pm = mContext.getPackageManager();
         final String[] packages = pm.getPackagesForUid(uid);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8cc9375..621e37b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -842,6 +842,8 @@
 
     /** Component used to install ephemeral applications */
     ComponentName mInstantAppInstallerComponent;
+    /** Component used to show resolver settings for Instant Apps */
+    ComponentName mInstantAppResolverSettingsComponent;
     ActivityInfo mInstantAppInstallerActivity;
     final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo();
 
@@ -2890,6 +2892,7 @@
                 mInstantAppResolverConnection = null;
             }
             updateInstantAppInstallerLocked();
+            mInstantAppResolverSettingsComponent = getEphemeralResolverSettingsLPr();
 
             // Read and update the usage of dex files.
             // Do this at the end of PM init so that all the packages have their
@@ -3196,6 +3199,37 @@
         }
     }
 
+    private @Nullable ComponentName getEphemeralResolverSettingsLPr() {
+        final Intent intent = new Intent(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        final int resolveFlags =
+                MATCH_DIRECT_BOOT_AWARE
+                | MATCH_DIRECT_BOOT_UNAWARE
+                | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0);
+        final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null,
+                resolveFlags, UserHandle.USER_SYSTEM);
+        Iterator<ResolveInfo> iter = matches.iterator();
+        while (iter.hasNext()) {
+            final ResolveInfo rInfo = iter.next();
+            final PackageSetting ps = mSettings.mPackages.get(rInfo.activityInfo.packageName);
+            if (ps != null) {
+                final PermissionsState permissionsState = ps.getPermissionsState();
+                if (permissionsState.hasPermission(Manifest.permission.ACCESS_INSTANT_APPS, 0)) {
+                    continue;
+                }
+            }
+            iter.remove();
+        }
+        if (matches.size() == 0) {
+            return null;
+        } else if (matches.size() == 1) {
+            return matches.get(0).getComponentInfo().getComponentName();
+        } else {
+            throw new RuntimeException(
+                    "There must be at most one ephemeral resolver settings; found " + matches);
+        }
+    }
+
     private void primeDomainVerificationsLPw(int userId) {
         if (DEBUG_DOMAIN_VERIFICATION) {
             Slog.d(TAG, "Priming domain verifications in user " + userId);
@@ -5788,6 +5822,10 @@
                         return false;
                     }
                     if (ps.getInstantApp(userId)) {
+                        if (DEBUG_EPHEMERAL) {
+                            Slog.v(TAG, "DENY instant app installed;"
+                                    + " pkg: " + packageName);
+                        }
                         return false;
                     }
                 }
@@ -6267,7 +6305,6 @@
                         intent, resolvedType, flags, userId), userId);
                 addEphemeral = !ephemeralDisabled
                         && isEphemeralAllowed(intent, result, userId, false /*skipPackageCheck*/);
-
                 // Check for cross profile results.
                 boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
                 xpResolveInfo = queryCrossProfileIntents(
@@ -6323,7 +6360,7 @@
                 } else {
                     // the caller wants to resolve for a particular package; however, there
                     // were no installed results, so, try to find an ephemeral result
-                    addEphemeral =  !ephemeralDisabled
+                    addEphemeral = !ephemeralDisabled
                             && isEphemeralAllowed(
                                     intent, null /*result*/, userId, true /*skipPackageCheck*/);
                     result = new ArrayList<ResolveInfo>();
@@ -23073,6 +23110,13 @@
         }
 
         @Override
+        public boolean isInstantAppInstallerComponent(ComponentName component) {
+            synchronized (mPackages) {
+                return component != null && component.equals(mInstantAppInstallerComponent);
+            }
+        }
+
+        @Override
         public void pruneInstantApps() {
             synchronized (mPackages) {
                 mInstantAppRegistry.pruneInstantAppsLPw();
@@ -23313,4 +23357,9 @@
         }
         return checkUidPermission(appOpPermission, uid) == PERMISSION_GRANTED;
     }
+
+    @Override
+    public ComponentName getInstantAppResolverSettingsComponent() {
+        return mInstantAppResolverSettingsComponent;
+    }
 }
diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java
index 904d915..6a0b648 100644
--- a/services/core/java/com/android/server/storage/AppFuseBridge.java
+++ b/services/core/java/com/android/server/storage/AppFuseBridge.java
@@ -21,7 +21,9 @@
 import android.system.Os;
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.FuseUnavailableMountException;
 import com.android.internal.util.Preconditions;
+import com.android.server.NativeDaemonConnectorException;
 import libcore.io.IoUtils;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -54,17 +56,17 @@
     }
 
     public ParcelFileDescriptor addBridge(MountScope mountScope)
-            throws BridgeException {
+            throws FuseUnavailableMountException, NativeDaemonConnectorException {
         try {
             synchronized (this) {
                 Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0);
                 if (mNativeLoop == 0) {
-                    throw new BridgeException("The thread has already been terminated");
+                    throw new FuseUnavailableMountException(mountScope.mountId);
                 }
                 final int fd = native_add_bridge(
-                        mNativeLoop, mountScope.mountId, mountScope.deviceFd.detachFd());
+                        mNativeLoop, mountScope.mountId, mountScope.open().detachFd());
                 if (fd == -1) {
-                    throw new BridgeException("Failed to invoke native_add_bridge");
+                    throw new FuseUnavailableMountException(mountScope.mountId);
                 }
                 final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd);
                 mScopes.put(mountScope.mountId, mountScope);
@@ -86,12 +88,12 @@
     }
 
     public ParcelFileDescriptor openFile(int pid, int mountId, int fileId, int mode)
-            throws FileNotFoundException, SecurityException, InterruptedException {
+            throws FuseUnavailableMountException, InterruptedException {
         final MountScope scope;
         synchronized (this) {
             scope = mScopes.get(mountId);
             if (scope == null) {
-                throw new FileNotFoundException("Cannot find mount point");
+                throw new FuseUnavailableMountException(mountId);
             }
         }
         if (scope.pid != pid) {
@@ -99,17 +101,14 @@
         }
         final boolean result = scope.waitForMount();
         if (result == false) {
-            throw new FileNotFoundException("Mount failed");
+            throw new FuseUnavailableMountException(mountId);
         }
         try {
-            if (Os.stat(scope.mountPoint.getPath()).st_ino != 1) {
-                throw new FileNotFoundException("Could not find bridge mount point.");
-            }
-        } catch (ErrnoException e) {
-            throw new FileNotFoundException(
-                    "Failed to stat mount point: " + scope.mountPoint.getParent());
+            return ParcelFileDescriptor.open(
+                    new File(scope.mountPoint, String.valueOf(fileId)), mode);
+        } catch (FileNotFoundException error) {
+            throw new FuseUnavailableMountException(mountId);
         }
-        return ParcelFileDescriptor.open(new File(scope.mountPoint, String.valueOf(fileId)), mode);
     }
 
     // Used by com_android_server_storage_AppFuse.cpp.
@@ -130,20 +129,18 @@
         }
     }
 
-    public static class MountScope implements AutoCloseable {
+    public static abstract class MountScope implements AutoCloseable {
         public final int uid;
         public final int pid;
         public final int mountId;
-        public final ParcelFileDescriptor deviceFd;
         public final File mountPoint;
         private final CountDownLatch mMounted = new CountDownLatch(1);
         private boolean mMountResult = false;
 
-        public MountScope(int uid, int pid, int mountId, ParcelFileDescriptor deviceFd) {
+        public MountScope(int uid, int pid, int mountId) {
             this.uid = uid;
             this.pid = pid;
             this.mountId = mountId;
-            this.deviceFd = deviceFd;
             this.mountPoint = new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE,  uid, mountId));
         }
 
@@ -161,16 +158,7 @@
             return mMountResult;
         }
 
-        @Override
-        public void close() throws Exception {
-            deviceFd.close();
-        }
-    }
-
-    public static class BridgeException extends Exception {
-        public BridgeException(String message) {
-            super(message);
-        }
+        public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException;
     }
 
     private native long native_new();
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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8f391a7..aa85574 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -175,6 +175,7 @@
     private boolean mTmpRecoveringMemory;
     private boolean mUpdateImeTarget;
     private boolean mTmpInitial;
+    private int mMaxUiWidth;
 
     // Mapping from a token IBinder to a WindowToken object on this display.
     private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
@@ -1559,6 +1560,39 @@
         }
     }
 
+    /** Sets the maximum width the screen resolution can be */
+    void setMaxUiWidth(int width) {
+        if (DEBUG_DISPLAY) {
+            Slog.v(TAG_WM, "Setting max ui width:" + width + " on display:" + getDisplayId());
+        }
+
+        mMaxUiWidth = width;
+
+        // Update existing metrics.
+        updateBaseDisplayMetrics(mBaseDisplayWidth, mBaseDisplayHeight, mBaseDisplayDensity);
+    }
+
+    /** Update base (override) display metrics. */
+    void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) {
+        mBaseDisplayWidth = baseWidth;
+        mBaseDisplayHeight = baseHeight;
+        mBaseDisplayDensity = baseDensity;
+
+        if (mMaxUiWidth > 0 && mBaseDisplayWidth > mMaxUiWidth) {
+            mBaseDisplayHeight = (mMaxUiWidth * mBaseDisplayHeight) / mBaseDisplayWidth;
+            mBaseDisplayDensity = (mMaxUiWidth * mBaseDisplayDensity) / mBaseDisplayWidth;
+            mBaseDisplayWidth = mMaxUiWidth;
+
+            if (DEBUG_DISPLAY) {
+                Slog.v(TAG_WM, "Applying config restraints:" + mBaseDisplayWidth + "x"
+                        + mBaseDisplayHeight + " at density:" + mBaseDisplayDensity
+                        + " on display:" + getDisplayId());
+            }
+        }
+
+        mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+    }
+
     void getContentRect(Rect out) {
         out.set(mContentRect);
     }
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/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fa9c2a7..0dc74d7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -385,6 +385,7 @@
     final boolean mAllowBootMessages;
 
     final boolean mLimitedAlphaCompositing;
+    final int mMaxUiWidth;
 
     final WindowManagerPolicy mPolicy;
 
@@ -949,6 +950,8 @@
                 com.android.internal.R.integer.config_drawLockTimeoutMillis);
         mAllowAnimationsInLowPowerMode = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
+        mMaxUiWidth = context.getResources().getInteger(
+                com.android.internal.R.integer.config_maxUiWidth);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mDisplaySettings = new DisplaySettings();
@@ -4572,6 +4575,9 @@
 
         synchronized(mWindowMap) {
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            if (mMaxUiWidth > 0) {
+                displayContent.setMaxUiWidth(mMaxUiWidth);
+            }
             readForcedDisplayPropertiesLocked(displayContent);
             mDisplayReady = true;
         }
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/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 9b4de043..70c7e58 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -26,6 +26,7 @@
 import android.os.Message;
 import android.os.SystemClock;
 import android.util.Log;
+import android.util.LongSparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -44,12 +45,21 @@
 
     // If this value changes, update DevicePolicyManager#retrieveNetworkLogs() javadoc
     private static final int MAX_EVENTS_PER_BATCH = 1200;
-    private static final long BATCH_FINALIZATION_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(90);
-    private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS =
-            TimeUnit.MINUTES.toMillis(30);
+
+    /**
+     * Maximum number of batches to store in memory. If more batches are generated and the DO
+     * doesn't fetch them, we will discard the oldest one.
+     */
+    private static final int MAX_BATCHES = 5;
+
+    private static final long BATCH_FINALIZATION_TIMEOUT_MS = 90 * 60 * 1000; // 1.5h
+    private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS = 30 * 60 * 1000; // 30m
 
     private static final String NETWORK_LOGGING_TIMEOUT_ALARM_TAG = "NetworkLogging.batchTimeout";
 
+    /** Delay after which older batches get discarded after a retrieval. */
+    private static final long RETRIEVED_BATCH_DISCARD_DELAY_MS = 5 * 60 * 1000; // 5m
+
     private final DevicePolicyManagerService mDpm;
     private final AlarmManager mAlarmManager;
 
@@ -66,22 +76,27 @@
 
     static final int LOG_NETWORK_EVENT_MSG = 1;
 
-    // threadsafe as it's Handler's thread confined
+    /** Network events accumulated so far to be finalized into a batch at some point. */
     @GuardedBy("this")
-    private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<NetworkEvent>();
+    private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
 
+    /**
+     * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the DO. Already
+     * retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}.
+     */
     @GuardedBy("this")
-    private ArrayList<NetworkEvent> mFullBatch;
+    private final LongSparseArray<ArrayList<NetworkEvent>> mBatches =
+            new LongSparseArray<>(MAX_BATCHES);
 
     @GuardedBy("this")
     private boolean mPaused = false;
 
     // each full batch is represented by its token, which the DPC has to provide back to retrieve it
     @GuardedBy("this")
-    private long mCurrentFullBatchToken;
+    private long mCurrentBatchToken;
 
     @GuardedBy("this")
-    private long mLastRetrievedFullBatchToken;
+    private long mLastRetrievedBatchToken;
 
     NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
         super(looper);
@@ -93,7 +108,7 @@
     public void handleMessage(Message msg) {
         switch (msg.what) {
             case LOG_NETWORK_EVENT_MSG: {
-                NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY);
+                final NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY);
                 if (networkEvent != null) {
                     synchronized (NetworkLoggingHandler.this) {
                         mNetworkEvents.add(networkEvent);
@@ -113,6 +128,8 @@
 
     void scheduleBatchFinalization() {
         final long when = SystemClock.elapsedRealtime() + BATCH_FINALIZATION_TIMEOUT_MS;
+        // We use alarm manager and not just postDelayed here to ensure the batch gets finalized
+        // even if the device goes to sleep.
         mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, when,
                 BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS, NETWORK_LOGGING_TIMEOUT_ALARM_TAG,
                 mBatchTimeoutAlarmListener, this);
@@ -131,62 +148,80 @@
             return;
         }
 
-        Log.d(TAG, "Resumed network logging. Current batch="
-                + mCurrentFullBatchToken + ", LastRetrievedBatch=" + mLastRetrievedFullBatchToken);
+        Log.d(TAG, "Resumed network logging. Current batch=" + mCurrentBatchToken
+                + ", LastRetrievedBatch=" + mLastRetrievedBatchToken);
         mPaused = false;
 
-        // If there is a full batch ready that the device owner hasn't been notified about, do it
-        // now.
-        if (mFullBatch != null && mFullBatch.size() > 0
-                && mLastRetrievedFullBatchToken != mCurrentFullBatchToken) {
+        // If there is a batch ready that the device owner hasn't been notified about, do it now.
+        if (mBatches.size() > 0 && mLastRetrievedBatchToken != mCurrentBatchToken) {
             scheduleBatchFinalization();
             notifyDeviceOwnerLocked();
         }
     }
 
     synchronized void discardLogs() {
-        mFullBatch = null;
-        mNetworkEvents = new ArrayList<NetworkEvent>();
+        mBatches.clear();
+        mNetworkEvents = new ArrayList<>();
         Log.d(TAG, "Discarded all network logs");
     }
 
     @GuardedBy("this")
     private void finalizeBatchAndNotifyDeviceOwnerLocked() {
         if (mNetworkEvents.size() > 0) {
-            // finalize the batch and start a new one from scratch
-            mFullBatch = mNetworkEvents;
-            mCurrentFullBatchToken++;
-            mNetworkEvents = new ArrayList<NetworkEvent>();
+            // Finalize the batch and start a new one from scratch.
+            if (mBatches.size() >= MAX_BATCHES) {
+                // Remove the oldest batch if we hit the limit.
+                mBatches.removeAt(0);
+            }
+            mCurrentBatchToken++;
+            mBatches.append(mCurrentBatchToken, mNetworkEvents);
+            mNetworkEvents = new ArrayList<>();
             if (!mPaused) {
                 notifyDeviceOwnerLocked();
             }
         } else {
-            // don't notify the DO, since there are no events; DPC can still retrieve
+            // Don't notify the DO, since there are no events; DPC can still retrieve
             // the last full batch if not paused.
             Log.d(TAG, "Was about to finalize the batch, but there were no events to send to"
-                    + " the DPC, the batchToken of last available batch: "
-                    + mCurrentFullBatchToken);
+                    + " the DPC, the batchToken of last available batch: " + mCurrentBatchToken);
         }
-        // regardless of whether the batch was non-empty schedule a new finalization after timeout
+        // Regardless of whether the batch was non-empty schedule a new finalization after timeout.
         scheduleBatchFinalization();
     }
 
+    /** Sends a notification to the DO. Should only be called when there is a batch available. */
     @GuardedBy("this")
     private void notifyDeviceOwnerLocked() {
-        Bundle extras = new Bundle();
-        extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken);
-        extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size());
+        final Bundle extras = new Bundle();
+        final int lastBatchSize = mBatches.valueAt(mBatches.size() - 1).size();
+        extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentBatchToken);
+        extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, lastBatchSize);
         Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
-                + mCurrentFullBatchToken);
+                + mCurrentBatchToken);
         mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
     }
 
-    synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) {
-        if (batchToken != mCurrentFullBatchToken) {
+    synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) {
+        final int index = mBatches.indexOfKey(batchToken);
+        if (index < 0) {
+            // Invalid token or batch has already been discarded.
             return null;
         }
-        mLastRetrievedFullBatchToken = mCurrentFullBatchToken;
-        return mFullBatch;
+
+        // Schedule this and older batches to be discarded after a delay to lessen memory load
+        // without interfering with the admin's ability to collect logs out-of-order.
+        // It isn't critical and we allow it to be delayed further if the phone sleeps, so we don't
+        // use the alarm manager here.
+        postDelayed(() -> {
+            synchronized(this) {
+                while (mBatches.size() > 0 && mBatches.keyAt(0) <= batchToken) {
+                    mBatches.removeAt(0);
+                }
+            }
+        }, RETRIEVED_BATCH_DISCARD_DELAY_MS);
+
+        mLastRetrievedBatchToken = batchToken;
+        return mBatches.valueAt(index);
     }
 }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f0732dd..da49eb3 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -317,7 +317,7 @@
 
             // In case the runtime switched since last boot (such as when
             // the old runtime was removed in an OTA), set the system
-            // property so that it is in sync. We can't do this in
+            // property so that it is in sync. We can | xq oqi't do this in
             // libnativehelper's JniInvocation::Init code where we already
             // had to fallback to a different runtime because it is
             // running as root and we need to be the system user to set
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 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/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index 9356dac..7790698 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -20,6 +20,7 @@
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.Manifest;
+import android.annotation.CheckResult;
 import android.annotation.Nullable;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -36,8 +37,11 @@
 import android.net.NetworkPolicyManager;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
+import android.os.IInterface;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -68,11 +72,13 @@
 import java.util.function.Function;
 
 //TODO move to own package!
-//TODO un/linkToDeath & onBinderDied - unbind
 //TODO onStop schedule unbind in 5 seconds
-//TODO Prune association on app uninstall
+//TODO make sure APIs are only callable from currently focused app
+//TODO schedule stopScan on activity destroy(except if configuration change)
+//TODO on associate called again after configuration change -> replace old callback with new
+//TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example)
 /** @hide */
-public class CompanionDeviceManagerService extends SystemService {
+public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
 
     private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
             CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
@@ -90,6 +96,8 @@
     private final CompanionDeviceManagerImpl mImpl;
     private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
     private IDeviceIdleController mIdleController;
+    private IFindDeviceCallback mFindDeviceCallback;
+    private ServiceConnection mServiceConnection;
 
     public CompanionDeviceManagerService(Context context) {
         super(context);
@@ -125,7 +133,51 @@
         publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
     }
 
+    @Override
+    public void binderDied() {
+        Handler.getMain().post(this::handleBinderDied);
+    }
+
+    private void handleBinderDied() {
+        mServiceConnection = unbind(mServiceConnection);
+        mFindDeviceCallback = unlinkToDeath(mFindDeviceCallback, this, 0);
+    }
+
+    /**
+     * Usage: {@code a = unlinkToDeath(a, deathRecipient, flags); }
+     */
+    @Nullable
+    @CheckResult
+    private static <T extends IInterface> T unlinkToDeath(T iinterface,
+            IBinder.DeathRecipient deathRecipient, int flags) {
+        if (iinterface != null) {
+            iinterface.asBinder().unlinkToDeath(deathRecipient, flags);
+        }
+        return null;
+    }
+
+    @Nullable
+    @CheckResult
+    private ServiceConnection unbind(@Nullable ServiceConnection conn) {
+        if (conn != null) {
+            getContext().unbindService(conn);
+        }
+        return null;
+    }
+
     class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+
+        @Override
+        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                throws RemoteException {
+            try {
+                return super.onTransact(code, data, reply, flags);
+            } catch (Throwable e) {
+                Slog.e(LOG_TAG, "Error during IPC", e);
+                throw ExceptionUtils.propagate(e, RemoteException.class);
+            }
+        }
+
         @Override
         public void associate(
                 AssociationRequest request,
@@ -135,14 +187,14 @@
                 Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
                         + ", callingPackage = " + callingPackage + ")");
             }
-            checkNotNull(request);
-            checkNotNull(callback);
+            checkNotNull(request, "Request cannot be null");
+            checkNotNull(callback, "Callback cannot be null");
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 //TODO bindServiceAsUser
                 getContext().bindService(
                         new Intent().setComponent(SERVICE_TO_BIND_TO),
-                        getServiceConnection(request, callback, callingPackage),
+                        createServiceConnection(request, callback, callingPackage),
                         Context.BIND_AUTO_CREATE);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
@@ -168,11 +220,11 @@
         return UserHandle.getUserId(Binder.getCallingUid());
     }
 
-    private ServiceConnection getServiceConnection(
+    private ServiceConnection createServiceConnection(
             final AssociationRequest request,
             final IFindDeviceCallback findDeviceCallback,
             final String callingPackage) {
-        return new ServiceConnection() {
+        mServiceConnection = new ServiceConnection() {
             @Override
             public void onServiceConnected(ComponentName name, IBinder service) {
                 if (DEBUG) {
@@ -180,6 +232,14 @@
                             "onServiceConnected(name = " + name + ", service = "
                                     + service + ")");
                 }
+                mFindDeviceCallback = findDeviceCallback;
+                try {
+                    mFindDeviceCallback.asBinder().linkToDeath(
+                            CompanionDeviceManagerService.this, 0);
+                } catch (RemoteException e) {
+                    handleBinderDied();
+                    return;
+                }
                 try {
                     ICompanionDeviceDiscoveryService.Stub
                             .asInterface(service)
@@ -198,6 +258,7 @@
                 if (DEBUG) Slog.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
             }
         };
+        return mServiceConnection;
     }
 
     private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() {
diff --git a/services/tests/servicestests/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/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 29c6f89..dbba727 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -19,6 +19,7 @@
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.SNOOZE_NEVER;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
@@ -26,8 +27,15 @@
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.KB_IN_BYTES;
 import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
+import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
+import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
+import static android.telephony.CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG;
+import static android.telephony.CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG;
+import static android.telephony.CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.Time.TIMEZONE_UTC;
@@ -94,18 +102,24 @@
 import android.net.NetworkTemplate;
 import android.os.Binder;
 import android.os.INetworkManagementService;
+import android.os.PersistableBundle;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.text.format.Time;
 import android.util.Log;
 import android.util.TrustedTime;
 
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -143,6 +157,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.util.Arrays;
+import java.util.Calendar;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -200,6 +215,9 @@
     private @Mock INotificationManager mNotifManager;
     private @Mock PackageManager mPackageManager;
     private @Mock IPackageManager mIpm;
+    private @Mock SubscriptionManager mSubscriptionManager;
+    private @Mock CarrierConfigManager mCarrierConfigManager;
+    private @Mock TelephonyManager mTelephonyManager;
 
     private static ActivityManagerInternal mActivityManagerInternal;
 
@@ -214,6 +232,12 @@
     private long mElapsedRealtime;
 
     private static final int USER_ID = 0;
+    private static final int FAKE_SUB_ID = 3737373;
+    private static final String FAKE_SUBSCRIBER_ID = "FAKE_SUB_ID";
+    private static final int DEFAULT_CYCLE_DAY = 1;
+    private static final int INVALID_CARRIER_CONFIG_VALUE = -9999;
+    private long mDefaultWarningBytes; // filled in with the actual default before tests are run
+    private long mDefaultLimitBytes; // filled in with the actual default before tests are run
 
     private static final int APP_ID_A = android.os.Process.FIRST_APPLICATION_UID + 4;
     private static final int APP_ID_B = android.os.Process.FIRST_APPLICATION_UID + 8;
@@ -235,6 +259,8 @@
 
     @BeforeClass
     public static void registerLocalServices() {
+        final PowerManagerInternal powerManager = addLocalServiceMock(PowerManagerInternal.class);
+        when(powerManager.getLowPowerState(anyInt())).thenReturn(mock(PowerSaveState.class));
         addLocalServiceMock(DeviceIdleController.LocalService.class);
         final UsageStatsManagerInternal usageStats =
                 addLocalServiceMock(UsageStatsManagerInternal.class);
@@ -255,7 +281,8 @@
 
         setCurrentTimeMillis(TEST_START);
 
-        // intercept various broadcasts, and pretend that uids have packages
+        // Intercept various broadcasts, and pretend that uids have packages.
+        // Also return mock service instances for a few critical services.
         mServiceContext = new BroadcastInterceptingContext(context) {
             @Override
             public PackageManager getPackageManager() {
@@ -266,6 +293,20 @@
             public void startActivity(Intent intent) {
                 // ignored
             }
+
+            @Override
+            public Object getSystemService(String name) {
+                switch (name) {
+                    case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
+                        return mSubscriptionManager;
+                    case Context.CARRIER_CONFIG_SERVICE:
+                        return mCarrierConfigManager;
+                    case Context.TELEPHONY_SERVICE:
+                        return mTelephonyManager;
+                    default:
+                        return super.getSystemService(name);
+                }
+            }
         };
 
         setNetpolicyXml(context);
@@ -321,6 +362,10 @@
               ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
         verify(mNetworkManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
+
+        NetworkPolicy defaultPolicy = mService.buildDefaultMobilePolicy(0, "");
+        mDefaultWarningBytes = defaultPolicy.warningBytes;
+        mDefaultLimitBytes = defaultPolicy.limitBytes;
     }
 
     @After
@@ -1132,6 +1177,269 @@
         }
     }
 
+    private void assertCycleDayAsExpected(PersistableBundle config, int carrierCycleDay,
+            boolean expectValid) {
+        config.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, carrierCycleDay);
+        int actualCycleDay = mService.getCycleDayFromCarrierConfig(config,
+                INVALID_CARRIER_CONFIG_VALUE);
+        if (expectValid) {
+            assertEquals(carrierCycleDay, actualCycleDay);
+        } else {
+            // INVALID_CARRIER_CONFIG_VALUE is returned for invalid values
+            assertEquals(INVALID_CARRIER_CONFIG_VALUE, actualCycleDay);
+        }
+    }
+
+    @Test
+    public void testGetCycleDayFromCarrierConfig() {
+        PersistableBundle config = CarrierConfigManager.getDefaultConfig();
+        final Calendar cal = Calendar.getInstance();
+        int actualCycleDay;
+
+        config.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, DATA_CYCLE_USE_PLATFORM_DEFAULT);
+        actualCycleDay = mService.getCycleDayFromCarrierConfig(config, DEFAULT_CYCLE_DAY);
+        assertEquals(DEFAULT_CYCLE_DAY, actualCycleDay);
+
+        // null config returns a default value
+        actualCycleDay = mService.getCycleDayFromCarrierConfig(null, DEFAULT_CYCLE_DAY);
+        assertEquals(DEFAULT_CYCLE_DAY, actualCycleDay);
+
+        // Sane, non-default values
+        assertCycleDayAsExpected(config, 1, true);
+        assertCycleDayAsExpected(config, cal.getMaximum(Calendar.DAY_OF_MONTH), true);
+        assertCycleDayAsExpected(config, cal.getMinimum(Calendar.DAY_OF_MONTH), true);
+
+        // Invalid values
+        assertCycleDayAsExpected(config, 0, false);
+        assertCycleDayAsExpected(config, DATA_CYCLE_THRESHOLD_DISABLED, false);
+        assertCycleDayAsExpected(config, cal.getMaximum(Calendar.DAY_OF_MONTH) + 1, false);
+        assertCycleDayAsExpected(config, cal.getMinimum(Calendar.DAY_OF_MONTH) - 5, false);
+    }
+
+    private void assertWarningBytesAsExpected(PersistableBundle config, long carrierWarningBytes,
+            long expected) {
+        config.putLong(KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, carrierWarningBytes);
+        long actualWarning = mService.getWarningBytesFromCarrierConfig(config,
+                INVALID_CARRIER_CONFIG_VALUE);
+        assertEquals(expected, actualWarning);
+    }
+
+    @Test
+    public void testGetWarningBytesFromCarrierConfig() {
+        PersistableBundle config = CarrierConfigManager.getDefaultConfig();
+        long actualWarningBytes;
+
+        assertWarningBytesAsExpected(config, DATA_CYCLE_USE_PLATFORM_DEFAULT,
+                mDefaultWarningBytes);
+        assertWarningBytesAsExpected(config, DATA_CYCLE_THRESHOLD_DISABLED, WARNING_DISABLED);
+        assertWarningBytesAsExpected(config, 0, 0);
+        // not a valid value
+        assertWarningBytesAsExpected(config, -1000, INVALID_CARRIER_CONFIG_VALUE);
+
+        // null config returns a default value
+        actualWarningBytes = mService.getWarningBytesFromCarrierConfig(null, mDefaultWarningBytes);
+        assertEquals(mDefaultWarningBytes, actualWarningBytes);
+    }
+
+    private void assertLimitBytesAsExpected(PersistableBundle config,  long carrierWarningBytes,
+            long expected) {
+        config.putLong(KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, carrierWarningBytes);
+        long actualWarning = mService.getLimitBytesFromCarrierConfig(config,
+                INVALID_CARRIER_CONFIG_VALUE);
+        assertEquals(expected, actualWarning);
+    }
+
+    @Test
+    public void testGetLimitBytesFromCarrierConfig() {
+        PersistableBundle config = CarrierConfigManager.getDefaultConfig();
+        long actualLimitBytes;
+
+        assertLimitBytesAsExpected(config, DATA_CYCLE_USE_PLATFORM_DEFAULT,
+                mDefaultLimitBytes);
+        assertLimitBytesAsExpected(config, DATA_CYCLE_THRESHOLD_DISABLED, LIMIT_DISABLED);
+        assertLimitBytesAsExpected(config, 0, 0);
+        // not a valid value
+        assertLimitBytesAsExpected(config, -1000, INVALID_CARRIER_CONFIG_VALUE);
+
+        // null config returns a default value
+        actualLimitBytes = mService.getWarningBytesFromCarrierConfig(null, mDefaultLimitBytes);
+        assertEquals(mDefaultLimitBytes, actualLimitBytes);
+    }
+
+    private PersistableBundle setupUpdateMobilePolicyCycleTests() throws RemoteException {
+        when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
+        when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+        PersistableBundle bundle = CarrierConfigManager.getDefaultConfig();
+        when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(bundle);
+        setNetworkPolicies(buildDefaultFakeMobilePolicy());
+        return bundle;
+    }
+
+    @Test
+    public void testUpdateMobilePolicyCycleWithNullConfig() throws RemoteException {
+        when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
+        when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+        when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(null);
+        setNetworkPolicies(buildDefaultFakeMobilePolicy());
+        // smoke test to make sure no errors are raised
+        mServiceContext.sendBroadcast(
+                new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+        );
+        assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
+                true);
+    }
+
+    @Test
+    public void testUpdateMobilePolicyCycleWithInvalidConfig() throws RemoteException {
+        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+        // Test with an invalid CarrierConfig, there should be no changes or crashes.
+        bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, -100);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, -100);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, -100);
+        mServiceContext.sendBroadcast(
+                new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+        );
+
+        assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
+                true);
+    }
+
+    @Test
+    public void testUpdateMobilePolicyCycleWithDefaultConfig() throws RemoteException {
+        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+        // Test that we respect the platform values when told to
+        bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT,
+                DATA_CYCLE_USE_PLATFORM_DEFAULT);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
+                DATA_CYCLE_USE_PLATFORM_DEFAULT);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+                DATA_CYCLE_USE_PLATFORM_DEFAULT);
+        mServiceContext.sendBroadcast(
+                new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+        );
+
+        assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
+                true);
+    }
+
+    @Test
+    public void testUpdateMobilePolicyCycleWithUserOverrides() throws RemoteException {
+        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+
+        // inferred = false implies that a user manually modified this policy.
+        NetworkPolicy policy = buildDefaultFakeMobilePolicy();
+        policy.inferred = false;
+        setNetworkPolicies(policy);
+
+        bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, 9999);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+                DATA_CYCLE_THRESHOLD_DISABLED);
+        mServiceContext.sendBroadcast(
+                new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+        );
+
+        // The policy still shouldn't change, because we don't want to overwrite user settings.
+        assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
+                false);
+    }
+
+    @Test
+    public void testUpdateMobilePolicyCycleUpdatesDataCycle() throws RemoteException {
+        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+
+        bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, 9999);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, 9999);
+        mServiceContext.sendBroadcast(
+                new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+        );
+
+        assertNetworkPolicyEquals(31, 9999, 9999, true);
+    }
+
+    @Test
+    public void testUpdateMobilePolicyCycleDisableThresholds() throws RemoteException {
+        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+
+        bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
+                DATA_CYCLE_THRESHOLD_DISABLED);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+                DATA_CYCLE_THRESHOLD_DISABLED);
+        mServiceContext.sendBroadcast(
+                new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+        );
+
+        assertNetworkPolicyEquals(31, WARNING_DISABLED, LIMIT_DISABLED, true);
+    }
+
+    @Test
+    public void testUpdateMobilePolicyCycleRevertsToDefault() throws RemoteException {
+        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+
+        bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
+                DATA_CYCLE_THRESHOLD_DISABLED);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+                DATA_CYCLE_THRESHOLD_DISABLED);
+        mServiceContext.sendBroadcast(
+                new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+        );
+        assertNetworkPolicyEquals(31, WARNING_DISABLED, LIMIT_DISABLED, true);
+
+        // If the user switches carriers to one that doesn't use a CarrierConfig, we should revert
+        // to the default data limit and warning. The cycle date doesn't need to revert as it's
+        // arbitrary anyways.
+        bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT,
+                DATA_CYCLE_USE_PLATFORM_DEFAULT);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
+                DATA_CYCLE_USE_PLATFORM_DEFAULT);
+        bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+                DATA_CYCLE_USE_PLATFORM_DEFAULT);
+        mServiceContext.sendBroadcast(
+                new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+        );
+
+        assertNetworkPolicyEquals(31, mDefaultWarningBytes, mDefaultLimitBytes,
+                true);
+    }
+
+    private NetworkPolicy buildDefaultFakeMobilePolicy() {
+        NetworkPolicy p = mService.buildDefaultMobilePolicy(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID);
+        // set a deterministic cycle date
+        p.cycleDay = DEFAULT_CYCLE_DAY;
+        return p;
+    }
+
+    private static NetworkPolicy buildFakeMobilePolicy(int cycleDay, long warningBytes,
+            long limitBytes, boolean inferred){
+        final NetworkTemplate template = buildTemplateMobileAll(FAKE_SUBSCRIBER_ID);
+        return new NetworkPolicy(template, cycleDay, "America/Los_Angeles", warningBytes,
+                limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred);
+    }
+
+    private void assertNetworkPolicyEquals(int expectedCycleDay, long expectedWarningBytes,
+            long expectedLimitBytes, boolean expectedInferred) {
+        NetworkPolicy[] policies = mService.getNetworkPolicies(
+                mServiceContext.getOpPackageName());
+        assertEquals("Unexpected number of network policies", 1, policies.length);
+        NetworkPolicy actualPolicy = policies[0];
+        NetworkPolicy expectedPolicy = buildFakeMobilePolicy(expectedCycleDay, expectedWarningBytes,
+                expectedLimitBytes, expectedInferred);
+        assertEquals(expectedPolicy, actualPolicy);
+    }
+
     private static long parseTime(String time) {
         final Time result = new Time();
         result.parse3339(time);
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..1729cee 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,68 @@
         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());
+    }
+
+    /**
+     * This tests setting the maximum ui width on a display.
+     */
+    @Test
+    public void testMaxUiWidth() throws Exception {
+        final int baseWidth = 1440;
+        final int baseHeight = 2560;
+        final int baseDensity = 300;
+
+        sDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
+
+        final int maxWidth = 300;
+        final int resultingHeight = (maxWidth * baseHeight) / baseWidth;
+        final int resultingDensity = (maxWidth * baseDensity) / baseWidth;
+
+        sDisplayContent.setMaxUiWidth(maxWidth);
+        verifySizes(sDisplayContent, maxWidth, resultingHeight, resultingDensity);
+
+        // Assert setting values again does not change;
+        sDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
+        verifySizes(sDisplayContent, maxWidth, resultingHeight, resultingDensity);
+
+        final int smallerWidth = 200;
+        final int smallerHeight = 400;
+        final int smallerDensity = 100;
+
+        // Specify smaller dimension, verify that it is honored
+        sDisplayContent.updateBaseDisplayMetrics(smallerWidth, smallerHeight, smallerDensity);
+        verifySizes(sDisplayContent, smallerWidth, smallerHeight, smallerDensity);
+
+        // Verify that setting the max width to a greater value than the base width has no effect
+        sDisplayContent.setMaxUiWidth(maxWidth);
+        verifySizes(sDisplayContent, smallerWidth, smallerHeight, smallerDensity);
+    }
+
+    private static void verifySizes(DisplayContent displayContent, int expectedBaseWidth,
+                             int expectedBaseHeight, int expectedBaseDensity) {
+        assertEquals(displayContent.mBaseDisplayWidth, expectedBaseWidth);
+        assertEquals(displayContent.mBaseDisplayHeight, expectedBaseHeight);
+        assertEquals(displayContent.mBaseDisplayDensity, expectedBaseDensity);
+    }
+
     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);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 1b28db7..e55e073 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -20,6 +20,7 @@
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.os.Build;
+import android.os.SystemProperties;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.TimeUtils;
@@ -56,6 +57,9 @@
     private static final boolean DEBUG = UsageStatsService.DEBUG;
     private static final String BAK_SUFFIX = ".bak";
     private static final String CHECKED_IN_SUFFIX = UsageStatsXml.CHECKED_IN_SUFFIX;
+    private static final String RETENTION_LEN_KEY = "ro.usagestats.chooser.retention";
+    private static final int SELECTION_LOG_RETENTION_LEN =
+            SystemProperties.getInt(RETENTION_LEN_KEY, 14);
 
     private final Object mLock = new Object();
     private final File[] mIntervalDirs;
@@ -504,7 +508,7 @@
                     mCal.getTimeInMillis());
 
             mCal.setTimeInMillis(currentTimeMillis);
-            mCal.addDays(-14);
+            mCal.addDays(-SELECTION_LOG_RETENTION_LEN);
             for (int i = 0; i < mIntervalDirs.length; ++i) {
                 pruneChooserCountsOlderThan(mIntervalDirs[i], mCal.getTimeInMillis());
             }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index f1cf441..0878e8b 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -554,7 +554,7 @@
                 boolean usbDataUnlocked) {
             if (DEBUG) {
                 Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
-                        + "forceRestart=" + forceRestart);
+                        + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
             }
 
             if (usbDataUnlocked != mUsbDataUnlocked) {
@@ -641,8 +641,11 @@
                 // Set the new USB configuration.
                 setUsbConfig(oemFunctions);
 
-                // Start up dependent services.
-                updateUsbStateBroadcastIfNeeded(true);
+                if (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
+                        || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP)) {
+                    // Start up dependent services.
+                    updateUsbStateBroadcastIfNeeded(true);
+                }
 
                 if (!waitForState(oemFunctions)) {
                     Slog.e(TAG, "Failed to switch USB config to " + functions);
@@ -878,7 +881,12 @@
                     setEnabledFunctions(functions, false, msg.arg1 == 1);
                     break;
                 case MSG_UPDATE_USER_RESTRICTIONS:
-                    setEnabledFunctions(mCurrentFunctions, false, mUsbDataUnlocked);
+                    // Restart the USB stack if USB transfer is enabled but no longer allowed.
+                    final boolean forceRestart = mUsbDataUnlocked
+                            && isUsbDataTransferActive()
+                            && !isUsbTransferAllowed();
+                    setEnabledFunctions(
+                            mCurrentFunctions, forceRestart, mUsbDataUnlocked && !forceRestart);
                     break;
                 case MSG_SYSTEM_READY:
                     updateUsbNotification();
@@ -902,12 +910,10 @@
                 case MSG_USER_SWITCHED: {
                     if (mCurrentUser != msg.arg1) {
                         // Restart the USB stack and re-apply user restrictions for MTP or PTP.
-                        final boolean active = UsbManager.containsFunction(mCurrentFunctions,
-                                UsbManager.USB_FUNCTION_MTP)
-                                || UsbManager.containsFunction(mCurrentFunctions,
-                                UsbManager.USB_FUNCTION_PTP);
-                        if (mUsbDataUnlocked && active && mCurrentUser != UserHandle.USER_NULL) {
-                            Slog.v(TAG, "Current user switched to " + mCurrentUser
+                        if (mUsbDataUnlocked
+                                && isUsbDataTransferActive()
+                                && mCurrentUser != UserHandle.USER_NULL) {
+                            Slog.v(TAG, "Current user switched to " + msg.arg1
                                     + "; resetting USB host stack for MTP or PTP");
                             // avoid leaking sensitive data from previous user
                             setEnabledFunctions(mCurrentFunctions, true, false);
@@ -928,6 +934,11 @@
             }
         }
 
+        private boolean isUsbDataTransferActive() {
+            return UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)
+                    || UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP);
+        }
+
         public UsbAccessory getCurrentAccessory() {
             return mCurrentAccessory;
         }
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index c790902..92233b1 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -125,8 +125,9 @@
     public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
 
     /**
-     * Extra key used to indicate the time (in millis) when the last outgoing emergency call was
-     * made.  This is used to identify potential emergency callbacks.
+     * Extra key used to indicate the time (in milliseconds since midnight, January 1, 1970 UTC)
+     * when the last outgoing emergency call was made.  This is used to identify potential emergency
+     * callbacks.
      */
     public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS =
             "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7a226a0..2b4bce3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -771,6 +771,13 @@
     public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
 
     /**
+     * Determines whether the Enhanced 4G LTE toggle will be shown in the settings. When this
+     * option is {@code true}, the toggle will be hidden regardless of whether the device and
+     * carrier supports 4G LTE or not.
+     */
+    public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
+
+    /**
      * Determine whether IMS apn can be shown.
      */
     public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
@@ -1522,6 +1529,7 @@
         sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
         sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true);
         sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
+        sDefaults.putBoolean(KEY_HIDE_ENHANCED_4G_LTE_BOOL, false);
         sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
         sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
         sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL, false);
diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java
index 74b20f4..0e8bad7 100644
--- a/telephony/java/com/android/ims/ImsException.java
+++ b/telephony/java/com/android/ims/ImsException.java
@@ -32,7 +32,7 @@
     }
 
     public ImsException(String message, int code) {
-        super(message + ", code = " + code);
+        super(message + "(" + code + ")");
         mCode = code;
     }
 
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index bcaac6e..73ee25a 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -261,7 +261,7 @@
      * by the system.
      */
     public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
-            = "android.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS";
+            = "com.android.internal.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS";
 
     /**
      * <p>Broadcast Action: Indicates that the action is forbidden by network.
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 506f406..960a2d9 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -1118,4 +1118,12 @@
     public int getInstallReason(String packageName, UserHandle user) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public ComponentName getInstantAppResolverSettingsComponent() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 04443a5..f22ad1d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -23,7 +23,12 @@
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.NetworkCapabilities.*;
 
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -33,6 +38,7 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivityManager.PacketKeepalive;
@@ -76,11 +82,16 @@
 import com.android.internal.util.WakeupMessage;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult;
 import com.android.server.net.NetworkPinner;
 
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -133,8 +144,19 @@
     private class MockContext extends BroadcastInterceptingContext {
         private final MockContentResolver mContentResolver;
 
+        @Spy private Resources mResources;
+
         MockContext(Context base) {
             super(base);
+
+            mResources = spy(base.getResources());
+            when(mResources.getStringArray(com.android.internal.R.array.networkAttributes)).
+                    thenReturn(new String[] {
+                            "wifi,1,1,1,-1,true",
+                            "mobile,0,0,0,-1,true",
+                            "mobile_mms,2,0,2,60000,true",
+                    });
+
             mContentResolver = new MockContentResolver();
             mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         }
@@ -150,6 +172,11 @@
         public ContentResolver getContentResolver() {
             return mContentResolver;
         }
+
+        @Override
+        public Resources getResources() {
+            return mResources;
+        }
     }
 
     /**
@@ -620,6 +647,7 @@
     private class WrappedConnectivityService extends ConnectivityService {
         public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
         private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
+        private MockableSystemProperties mSystemProperties;
 
         public WrappedConnectivityService(Context context, INetworkManagementService netManager,
                 INetworkStatsService statsService, INetworkPolicyManager policyManager,
@@ -629,9 +657,13 @@
         }
 
         @Override
-        protected int getDefaultTcpRwnd() {
-            // Prevent wrapped ConnectivityService from trying to write to SystemProperties.
-            return 0;
+        protected MockableSystemProperties getSystemProperties() {
+            // Minimal approach to overriding system properties: let most calls fall through to real
+            // device values, and only override ones values that are important to this test.
+            mSystemProperties = spy(new MockableSystemProperties());
+            when(mSystemProperties.getInt("net.tcp.default_init_rwnd", 0)).thenReturn(0);
+            when(mSystemProperties.getBoolean("ro.radio.noril", false)).thenReturn(false);
+            return mSystemProperties;
         }
 
         @Override
@@ -801,6 +833,14 @@
         return cv;
     }
 
+    public void testNetworkTypes() {
+        // Ensure that our mocks for the networkAttributes config variable work as expected. If they
+        // don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
+        // will fail. Failing here is much easier to debug.
+        assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
+        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
+    }
+
     @SmallTest
     public void testLingering() throws Exception {
         verifyNoNetwork();
diff --git a/tests/testables/src/android/testing/BaseFragmentTest.java b/tests/testables/src/android/testing/BaseFragmentTest.java
index 53841d5..b09bcde 100644
--- a/tests/testables/src/android/testing/BaseFragmentTest.java
+++ b/tests/testables/src/android/testing/BaseFragmentTest.java
@@ -133,14 +133,7 @@
     public void testRecreate() {
         mFragments.dispatchResume();
         processAllMessages();
-        mFragments.dispatchPause();
-        Parcelable p = mFragments.saveAllState();
-        mFragments.dispatchDestroy();
-
-        mFragments = FragmentController.createController(new HostCallbacks());
-        mFragments.attachHost(null);
-        mFragments.restoreAllState(p, (FragmentManagerNonConfig) null);
-        mFragments.dispatchResume();
+        recreateFragment();
         processAllMessages();
     }
 
@@ -154,6 +147,18 @@
         processAllMessages();
     }
 
+    protected void recreateFragment() {
+        mFragments.dispatchPause();
+        Parcelable p = mFragments.saveAllState();
+        mFragments.dispatchDestroy();
+
+        mFragments = FragmentController.createController(new HostCallbacks());
+        mFragments.attachHost(null);
+        mFragments.restoreAllState(p, (FragmentManagerNonConfig) null);
+        mFragments.dispatchResume();
+        mFragment = mFragments.getFragmentManager().findFragmentById(VIEW_ID);
+    }
+
     protected void attachFragmentToWindow() {
         ViewUtils.attachView(mView);
         TestableLooper.get(this).processMessages(1);
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 3330b1a..2bf5206 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1403,7 +1403,8 @@
             String8 src = it.getFile()->getPrintableSource();
             err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                     it.getFile(), &table, xmlFlags);
-            if (err == NO_ERROR) {
+            // Only verify IDs if there was no error and the file is non-empty.
+            if (err == NO_ERROR && it.getFile()->hasData()) {
                 ResXMLTree block;
                 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
                 checkForIds(src, block);
@@ -1550,7 +1551,7 @@
             String8 src = it.getFile()->getPrintableSource();
             err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                     it.getFile(), &table, xmlFlags);
-            if (err == NO_ERROR) {
+            if (err == NO_ERROR && it.getFile()->hasData()) {
                 ResXMLTree block;
                 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
                 checkForIds(src, block);
@@ -1598,7 +1599,7 @@
         err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
                              workItem.file, &table, xmlCompilationFlags);
 
-        if (err == NO_ERROR) {
+        if (err == NO_ERROR && workItem.file->hasData()) {
             assets->addResource(workItem.resPath.getPathLeaf(),
                                 workItem.resPath,
                                 workItem.file,
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 391aa47..221f3c2 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -78,6 +78,17 @@
                         ResourceTable* table,
                         int options)
 {
+    if (table->versionForCompat(bundle, resourceName, target, root)) {
+        // The file was versioned, so stop processing here.
+        // The resource entry has already been removed and the new one added.
+        // Remove the assets entry.
+        sp<AaptDir> resDir = assets->getDirs().valueFor(String8("res"));
+        sp<AaptDir> dir = resDir->getDirs().valueFor(target->getGroupEntry().toDirName(
+                target->getResourceType()));
+        dir->removeFile(target->getPath().getPathLeaf());
+        return NO_ERROR;
+    }
+
     if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
         root->removeWhitespace(true, NULL);
     } else  if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
@@ -4758,6 +4769,77 @@
     return false;
 }
 
+bool ResourceTable::versionForCompat(const Bundle* bundle, const String16& resourceName,
+                                         const sp<AaptFile>& target, const sp<XMLNode>& root) {
+    XMLNode* node = root.get();
+    while (node->getType() != XMLNode::TYPE_ELEMENT) {
+        // We're assuming the root element is what we're looking for, which can only be under a
+        // bunch of namespace declarations.
+        if (node->getChildren().size() != 1) {
+          // Not sure what to do, bail.
+          return false;
+        }
+        node = node->getChildren().itemAt(0).get();
+    }
+
+    if (node->getElementNamespace().size() != 0) {
+        // Not something we care about.
+        return false;
+    }
+
+    int versionedSdk = 0;
+    if (node->getElementName() == String16("adaptive-icon")) {
+        versionedSdk = SDK_O;
+    }
+
+    const int minSdkVersion = getMinSdkVersion(bundle);
+    const ConfigDescription config(target->getGroupEntry().toParams());
+    if (versionedSdk <= minSdkVersion || versionedSdk <= config.sdkVersion) {
+        return false;
+    }
+
+    sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
+            String16(target->getResourceType()), resourceName);
+    if (!shouldGenerateVersionedResource(cl, config, versionedSdk)) {
+        return false;
+    }
+
+    // Remove the original entry.
+    cl->removeEntry(config);
+
+    // We need to wholesale version this file.
+    ConfigDescription newConfig(config);
+    newConfig.sdkVersion = versionedSdk;
+    sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
+            AaptGroupEntry(newConfig), target->getResourceType());
+    String8 resPath = String8::format("res/%s/%s.xml",
+            newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
+            String8(resourceName).string());
+    resPath.convertToResPath();
+
+    // Add a resource table entry.
+    addEntry(SourcePos(),
+            String16(mAssets->getPackage()),
+            String16(target->getResourceType()),
+            resourceName,
+            String16(resPath),
+            NULL,
+            &newConfig);
+
+    // Schedule this to be compiled.
+    CompileResourceWorkItem item;
+    item.resourceName = resourceName;
+    item.resPath = resPath;
+    item.file = newFile;
+    item.xmlRoot = root->clone();
+    item.needsCompiling = false;    // This step occurs after we parse/assign, so we don't need
+                                    // to do it again.
+    mWorkQueue.push(item);
+
+    // Now mark the old entry as deleted.
+    return true;
+}
+
 status_t ResourceTable::modifyForCompat(const Bundle* bundle,
                                         const String16& resourceName,
                                         const sp<AaptFile>& target,
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index cf1e992..aff22d4 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -203,6 +203,9 @@
     size_t numLocalResources() const;
     bool hasResources() const;
 
+    bool versionForCompat(const Bundle* bundle, const String16& resourceName,
+                          const sp<AaptFile>& file, const sp<XMLNode>& root);
+
     status_t modifyForCompat(const Bundle* bundle);
     status_t modifyForCompat(const Bundle* bundle,
                              const String16& resourceName,
@@ -431,6 +434,10 @@
             mEntries.add(config, entry);
         }
         
+        void removeEntry(const ResTable_config& config) {
+            mEntries.removeItem(config);
+        }
+
         const DefaultKeyedVector<ConfigDescription, sp<Entry> >& getEntries() const { return mEntries; }
     private:
         const String16 mName;
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index e3bc34b..c20ee12 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -33,7 +33,6 @@
 import com.android.layoutlib.bridge.util.NinePatchInputStream;
 import com.android.ninepatch.NinePatch;
 import com.android.resources.ResourceType;
-import com.android.resources.ResourceUrl;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 import com.android.util.Pair;
 
@@ -60,8 +59,6 @@
 import java.io.InputStream;
 import java.util.Iterator;
 
-import static com.android.SdkConstants.ANDROID_NS_NAME;
-
 @SuppressWarnings("deprecation")
 public class Resources_Delegate {
 
@@ -140,8 +137,8 @@
 
             if (value == null) {
                 // Unable to resolve the attribute, just leave the unresolved value
-                value = new ResourceValue(ResourceUrl.create(resourceInfo.getFirst(), attributeName,
-                        platformResFlag_out[0]), attributeName);
+                value = new ResourceValue(resourceInfo.getFirst(), attributeName, attributeName,
+                        platformResFlag_out[0]);
             }
             return Pair.of(attributeName, value);
         }
@@ -681,7 +678,7 @@
         String packageName;
         if (resourceInfo != null) {
             if (platformOut[0]) {
-                packageName = ANDROID_NS_NAME;
+                packageName = SdkConstants.ANDROID_NS_NAME;
             } else {
                 packageName = resources.mContext.getPackageName();
                 packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName;
@@ -699,7 +696,7 @@
         Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
         if (resourceInfo != null) {
             if (platformOut[0]) {
-                return ANDROID_NS_NAME;
+                return SdkConstants.ANDROID_NS_NAME;
             }
             String packageName = resources.mContext.getPackageName();
             return packageName == null ? SdkConstants.APP_PREFIX : packageName;
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index e118889..80e3bad 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -233,7 +233,8 @@
             Map<String, ByteBuffer> bufferForPath) {
         FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
         for (FontConfig.Font font : family.getFonts()) {
-            FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, font.getFontName(),
+            String fullPathName = "/system/fonts/" + font.getFontName();
+            FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, fullPathName,
                     font.getWeight(), font.isItalic());
         }
         fontFamily.freeze();
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index 43f4ebc..e4b2020 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -127,6 +127,9 @@
 
     private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX,
             float lightPosY, float lightHeight, float lightSize, Canvas canvas) {
+        if (canvas == null || canvas.getWidth() == 0 || canvas.getHeight() == 0) {
+            return;
+        }
 
         // The polygon of shadow (same as the original item)
         float[] shadowPoly = new float[coordinates.length * 3];
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 432fdda..4573f7a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -31,7 +31,6 @@
 import com.android.layoutlib.bridge.impl.ParserFactory;
 import com.android.layoutlib.bridge.impl.Stack;
 import com.android.resources.ResourceType;
-import com.android.resources.ResourceUrl;
 import com.android.util.Pair;
 import com.android.util.PropertiesMap;
 import com.android.util.PropertiesMap.Property;
@@ -87,6 +86,7 @@
 import android.view.BridgeInflater;
 import android.view.Display;
 import android.view.DisplayAdjustments;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -107,7 +107,6 @@
 import java.util.Map;
 
 import static android.os._Original_Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static com.android.SdkConstants.ANDROID_NS_NAME;
 import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE;
 
 /**
@@ -122,23 +121,20 @@
 
     static {
         FRAMEWORK_PATCHED_VALUES.put("animateFirstView", new ResourceValue(
-                ResourceUrl.create(ANDROID_NS_NAME, ResourceType.BOOL, "animateFirstView"),
-                "false"));
-        FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges", new ResourceValue(
-                ResourceUrl.create(ANDROID_NS_NAME, ResourceType.BOOL, "animateLayoutChanges"),
-                "false"));
+                ResourceType.BOOL, "animateFirstView", "false", false));
+        FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges",
+                new ResourceValue(ResourceType.BOOL, "animateLayoutChanges", "false", false));
 
 
-        FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout", new ResourceValue(
-                ResourceUrl.create(ANDROID_NS_NAME, ResourceType.LAYOUT,
-                        "textEditSuggestionItemLayout"), "text_edit_suggestion_item"));
-        FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout", new ResourceValue(
-                ResourceUrl.create(ANDROID_NS_NAME, ResourceType.LAYOUT,
-                        "textEditSuggestionContainerLayout"), "text_edit_suggestion_container"));
-        FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle", new ResourceValue(
-                ResourceUrl.create(ANDROID_NS_NAME, ResourceType.STYLE,
-                        "textEditSuggestionHighlightStyle"),
-                "TextAppearance.Holo.SuggestionHighlight"));
+        FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout",
+                new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionItemLayout",
+                        "text_edit_suggestion_item", true));
+        FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout",
+                new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionContainerLayout",
+                        "text_edit_suggestion_container", true));
+        FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle",
+                new ResourceValue(ResourceType.STYLE, "textEditSuggestionHighlightStyle",
+                        "TextAppearance.Holo.SuggestionHighlight", true));
 
     }
 
@@ -648,6 +644,10 @@
             return null;
         }
 
+        if (AUDIO_SERVICE.equals(service)) {
+            return null;
+        }
+
         throw new UnsupportedOperationException("Unsupported Service: " + service);
     }
 
@@ -967,9 +967,7 @@
                     // there is a value in the XML, but we need to resolve it in case it's
                     // referencing another resource or a theme value.
                     ta.bridgeSetValue(index, attrName, frameworkAttr,
-                            mRenderResources.resolveResValue(new ResourceValue(
-                                    ResourceUrl.create(ResourceType.STRING, attrName,
-                                            isPlatformFile), value)));
+                            mRenderResources.resolveValue(null, attrName, value, isPlatformFile));
                 }
             }
         }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 906ebb1..53c3f90 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -916,4 +916,9 @@
     public boolean canRequestPackageInstalls() {
         return false;
     }
+
+    @Override
+    public ComponentName getInstantAppResolverSettingsComponent() {
+        return null;
+    }
 }
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
index 957831d..736b287 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
index 00dddee..8739b7f 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -311,7 +311,6 @@
         sFrameworkRepo = null;
         sProjectResources = null;
         sLogger = null;
-        sBridge.dispose();
         sBridge = null;
 
         TestUtils.gc();
@@ -329,7 +328,6 @@
         RenderSession session = sBridge.createSession(params);
 
         try {
-
             if (frameTimeNanos != -1) {
                 session.setElapsedFrameTimeNanos(frameTimeNanos);
             }
@@ -338,11 +336,13 @@
                 getLogger().error(session.getResult().getException(),
                         session.getResult().getErrorMessage());
             }
-            // Render the session with a timeout of 50s.
-            Result renderResult = session.render(50000);
-            if (!renderResult.isSuccess()) {
-                getLogger().error(session.getResult().getException(),
-                        session.getResult().getErrorMessage());
+            else {
+                // Render the session with a timeout of 50s.
+                Result renderResult = session.render(50000);
+                if (!renderResult.isSuccess()) {
+                    getLogger().error(session.getResult().getException(),
+                            session.getResult().getErrorMessage());
+                }
             }
 
             return RenderResult.getFromSession(session);
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 10ffd8a..1852feb 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -81,8 +81,6 @@
 
     boolean disableNetwork(int netId);
 
-    boolean pingSupplicant();
-
     void startScan(in ScanSettings requested, in WorkSource ws);
 
     List<ScanResult> getScanResults(String callingPackage);
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index eceb365..4387b0c 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -18,11 +18,10 @@
 
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -140,8 +139,8 @@
 
         // (1) connect + success
         mDut.attach(mockCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).connect(binder.capture(), anyString(),
-                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        inOrder.verify(mockAwareService).connect(binder.capture(), any(),
+                clientProxyCallback.capture(), isNull(), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -150,8 +149,7 @@
         // (2) publish - should succeed
         PublishConfig publishConfig = new PublishConfig.Builder().build();
         session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
-                any(IWifiAwareDiscoverySessionCallback.class));
+        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig), any());
 
         // (3) disconnect
         session.destroy();
@@ -163,8 +161,8 @@
 
         // (5) connect
         mDut.attach(mockCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).connect(binder.capture(), anyString(),
-                any(IWifiAwareEventCallback.class), (ConfigRequest) isNull(), eq(false));
+        inOrder.verify(mockAwareService).connect(binder.capture(), any(), any(), isNull(),
+                eq(false));
 
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
     }
@@ -185,16 +183,16 @@
 
         // (1) connect + failure
         mDut.attach(mockCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                isNull(), eq(false));
         clientProxyCallback.getValue().onConnectFail(reason);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttachFailed();
 
         // (2) connect + success
         mDut.attach(mockCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                isNull(), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -203,8 +201,7 @@
         // (4) subscribe: should succeed
         SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
         session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
-                any(IWifiAwareDiscoverySessionCallback.class));
+        inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig), any());
 
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
     }
@@ -223,19 +220,19 @@
 
         // (1) connect + success
         mDut.attach(mockCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                isNull(), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
-        inOrder.verify(mockCallback).onAttached(any(WifiAwareSession.class));
+        inOrder.verify(mockCallback).onAttached(any());
 
         // (2) connect + success
         mDut.attach(mockCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                isNull(), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId + 1);
         mMockLooper.dispatchAll();
-        inOrder.verify(mockCallback).onAttached(any(WifiAwareSession.class));
+        inOrder.verify(mockCallback).onAttached(any());
 
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
     }
@@ -278,8 +275,8 @@
 
         // (0) connect + success
         mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                eq(configRequest), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -370,8 +367,8 @@
 
         // (1) connect successfully
         mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                eq(configRequest), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -426,8 +423,8 @@
 
         // (0) connect + success
         mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                eq(configRequest), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -507,8 +504,8 @@
 
         // (1) connect successfully
         mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                eq(configRequest), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -899,8 +896,7 @@
         final RttManager.RttResult rttResults = new RttManager.RttResult();
         rttResults.distance = 10;
 
-        when(mockAwareService.startRanging(anyInt(), anyInt(),
-                any(RttManager.ParcelableRttParams.class))).thenReturn(rangingId);
+        when(mockAwareService.startRanging(anyInt(), anyInt(), any())).thenReturn(rangingId);
 
         InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
                 mockPublishSession, mockRttListener);
@@ -919,8 +915,8 @@
 
         // (1) connect successfully
         mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                eq(configRequest), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -994,8 +990,8 @@
 
         // (1) connect successfully
         mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                eq(configRequest), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -1085,8 +1081,8 @@
 
         // (1) connect successfully
         mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
-                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                eq(configRequest), eq(false));
         clientProxyCallback.getValue().onConnectSuccess(clientId);
         mMockLooper.dispatchAll();
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());