Merge "TextClassificationManager: Update model file reference."
diff --git a/api/current.txt b/api/current.txt
index 4981c43..2bc9764 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -751,6 +751,7 @@
     field public static final int isModifier = 16843334; // 0x1010246
     field public static final int isRepeatable = 16843336; // 0x1010248
     field public static final int isScrollContainer = 16843342; // 0x101024e
+    field public static final int isStatic = 16844125; // 0x101055d
     field public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
     field public static final int isolatedSplits = 16844109; // 0x101054d
@@ -6566,7 +6567,7 @@
 
   public static class AssistStructure.ViewNode {
     method public float getAlpha();
-    method public int getAutoFillHint();
+    method public java.lang.String[] getAutoFillHint();
     method public android.view.autofill.AutofillId getAutofillId();
     method public java.lang.String[] getAutofillOptions();
     method public int getAutofillType();
@@ -25193,8 +25194,8 @@
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
-    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
+    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback);
     method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
     method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
     method public deprecated void setNetworkPreference(int);
@@ -36796,11 +36797,11 @@
   }
 
   public static final class SaveInfo.Builder {
-    ctor public SaveInfo.Builder(int);
-    method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutofillId...);
+    ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
     method public android.service.autofill.SaveInfo build();
     method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
     method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender);
+    method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
   }
 
 }
@@ -39178,6 +39179,7 @@
     method public android.os.PersistableBundle getConfigForSubId(int);
     method public void notifyConfigChangedForSubId(int);
     field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
+    field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
     field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
     field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
     field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
@@ -39220,6 +39222,8 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
     field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
+    field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+    field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
     field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
     field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
@@ -39275,6 +39279,7 @@
     field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
     field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
     field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent";
+    field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
     field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
     field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
     field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
@@ -45098,7 +45103,7 @@
     method public float getAlpha();
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
-    method public int getAutofillHint();
+    method public java.lang.String[] getAutofillHint();
     method public int getAutofillMode();
     method public int getAutofillType();
     method public android.view.autofill.AutofillValue getAutofillValue();
@@ -45419,7 +45424,7 @@
     method public void setActivated(boolean);
     method public void setAlpha(float);
     method public void setAnimation(android.view.animation.Animation);
-    method public void setAutofillHint(int);
+    method public void setAutofillHint(java.lang.String[]);
     method public void setAutofillMode(int);
     method public void setBackground(android.graphics.drawable.Drawable);
     method public void setBackgroundColor(int);
@@ -45562,20 +45567,19 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
-    field public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 1; // 0x1
-    field public static final int AUTOFILL_HINT_NAME = 2; // 0x2
-    field public static final int AUTOFILL_HINT_NONE = 0; // 0x0
-    field public static final int AUTOFILL_HINT_PASSWORD = 8; // 0x8
-    field public static final int AUTOFILL_HINT_PHONE = 16; // 0x10
-    field public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 32; // 0x20
-    field public static final int AUTOFILL_HINT_POSTAL_CODE = 64; // 0x40
-    field public static final int AUTOFILL_HINT_USERNAME = 4; // 0x4
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
+    field public static final java.lang.String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
+    field public static final java.lang.String AUTOFILL_HINT_NAME = "name";
+    field public static final java.lang.String AUTOFILL_HINT_PASSWORD = "password";
+    field public static final java.lang.String AUTOFILL_HINT_PHONE = "phone";
+    field public static final java.lang.String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
+    field public static final java.lang.String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+    field public static final java.lang.String AUTOFILL_HINT_USERNAME = "username";
     field public static final int AUTOFILL_MODE_AUTO = 1; // 0x1
     field public static final int AUTOFILL_MODE_INHERIT = 0; // 0x0
     field public static final int AUTOFILL_MODE_MANUAL = 2; // 0x2
@@ -46237,7 +46241,7 @@
     method public abstract void setAccessibilityFocused(boolean);
     method public abstract void setActivated(boolean);
     method public abstract void setAlpha(float);
-    method public abstract void setAutofillHint(int);
+    method public abstract void setAutofillHint(java.lang.String[]);
     method public abstract void setAutofillOptions(java.lang.String[]);
     method public abstract void setAutofillType(int);
     method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
@@ -46248,6 +46252,7 @@
     method public abstract void setClickable(boolean);
     method public abstract void setContentDescription(java.lang.CharSequence);
     method public abstract void setContextClickable(boolean);
+    method public abstract void setDataIsSensitive(boolean);
     method public abstract void setDimens(int, int, int, int, int, int);
     method public abstract void setElevation(float);
     method public abstract void setEnabled(boolean);
@@ -46258,7 +46263,6 @@
     method public abstract void setInputType(int);
     method public abstract void setLongClickable(boolean);
     method public abstract void setOpaque(boolean);
-    method public abstract void setSanitized(boolean);
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
     method public abstract void setText(java.lang.CharSequence, int, int);
@@ -47533,6 +47537,10 @@
     method public int getListValue();
     method public java.lang.CharSequence getTextValue();
     method public boolean getToggleValue();
+    method public boolean isDate();
+    method public boolean isList();
+    method public boolean isText();
+    method public boolean isToggle();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR;
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index fbfee83..04c6a73 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -864,6 +864,7 @@
     field public static final int isModifier = 16843334; // 0x1010246
     field public static final int isRepeatable = 16843336; // 0x1010248
     field public static final int isScrollContainer = 16843342; // 0x101024e
+    field public static final int isStatic = 16844125; // 0x101055d
     field public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
     field public static final int isolatedSplits = 16844109; // 0x101054d
@@ -6816,7 +6817,7 @@
 
   public static class AssistStructure.ViewNode {
     method public float getAlpha();
-    method public int getAutoFillHint();
+    method public java.lang.String[] getAutoFillHint();
     method public android.view.autofill.AutofillId getAutofillId();
     method public java.lang.String[] getAutofillOptions();
     method public int getAutofillType();
@@ -27312,8 +27313,8 @@
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
-    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
+    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback);
     method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
     method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
     method public deprecated void setNetworkPreference(int);
@@ -39828,11 +39829,11 @@
   }
 
   public static final class SaveInfo.Builder {
-    ctor public SaveInfo.Builder(int);
-    method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutofillId...);
+    ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
     method public android.service.autofill.SaveInfo build();
     method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
     method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender);
+    method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
   }
 
 }
@@ -42536,6 +42537,7 @@
     method public void notifyConfigChangedForSubId(int);
     method public void updateConfigForPhoneId(int, java.lang.String);
     field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
+    field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
     field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
     field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
     field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
@@ -42578,6 +42580,8 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
     field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
+    field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+    field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
     field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
     field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
@@ -42633,6 +42637,7 @@
     field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
     field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
     field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent";
+    field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
     field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
     field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
     field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
@@ -48557,7 +48562,7 @@
     method public float getAlpha();
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
-    method public int getAutofillHint();
+    method public java.lang.String[] getAutofillHint();
     method public int getAutofillMode();
     method public int getAutofillType();
     method public android.view.autofill.AutofillValue getAutofillValue();
@@ -48878,7 +48883,7 @@
     method public void setActivated(boolean);
     method public void setAlpha(float);
     method public void setAnimation(android.view.animation.Animation);
-    method public void setAutofillHint(int);
+    method public void setAutofillHint(java.lang.String[]);
     method public void setAutofillMode(int);
     method public void setBackground(android.graphics.drawable.Drawable);
     method public void setBackgroundColor(int);
@@ -49021,20 +49026,19 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
-    field public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 1; // 0x1
-    field public static final int AUTOFILL_HINT_NAME = 2; // 0x2
-    field public static final int AUTOFILL_HINT_NONE = 0; // 0x0
-    field public static final int AUTOFILL_HINT_PASSWORD = 8; // 0x8
-    field public static final int AUTOFILL_HINT_PHONE = 16; // 0x10
-    field public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 32; // 0x20
-    field public static final int AUTOFILL_HINT_POSTAL_CODE = 64; // 0x40
-    field public static final int AUTOFILL_HINT_USERNAME = 4; // 0x4
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
+    field public static final java.lang.String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
+    field public static final java.lang.String AUTOFILL_HINT_NAME = "name";
+    field public static final java.lang.String AUTOFILL_HINT_PASSWORD = "password";
+    field public static final java.lang.String AUTOFILL_HINT_PHONE = "phone";
+    field public static final java.lang.String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
+    field public static final java.lang.String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+    field public static final java.lang.String AUTOFILL_HINT_USERNAME = "username";
     field public static final int AUTOFILL_MODE_AUTO = 1; // 0x1
     field public static final int AUTOFILL_MODE_INHERIT = 0; // 0x0
     field public static final int AUTOFILL_MODE_MANUAL = 2; // 0x2
@@ -49696,7 +49700,7 @@
     method public abstract void setAccessibilityFocused(boolean);
     method public abstract void setActivated(boolean);
     method public abstract void setAlpha(float);
-    method public abstract void setAutofillHint(int);
+    method public abstract void setAutofillHint(java.lang.String[]);
     method public abstract void setAutofillOptions(java.lang.String[]);
     method public abstract void setAutofillType(int);
     method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
@@ -49707,6 +49711,7 @@
     method public abstract void setClickable(boolean);
     method public abstract void setContentDescription(java.lang.CharSequence);
     method public abstract void setContextClickable(boolean);
+    method public abstract void setDataIsSensitive(boolean);
     method public abstract void setDimens(int, int, int, int, int, int);
     method public abstract void setElevation(float);
     method public abstract void setEnabled(boolean);
@@ -49717,7 +49722,6 @@
     method public abstract void setInputType(int);
     method public abstract void setLongClickable(boolean);
     method public abstract void setOpaque(boolean);
-    method public abstract void setSanitized(boolean);
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
     method public abstract void setText(java.lang.CharSequence, int, int);
@@ -50995,6 +50999,10 @@
     method public int getListValue();
     method public java.lang.CharSequence getTextValue();
     method public boolean getToggleValue();
+    method public boolean isDate();
+    method public boolean isList();
+    method public boolean isText();
+    method public boolean isToggle();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR;
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 3be7f67..f5883c9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -751,6 +751,7 @@
     field public static final int isModifier = 16843334; // 0x1010246
     field public static final int isRepeatable = 16843336; // 0x1010248
     field public static final int isScrollContainer = 16843342; // 0x101024e
+    field public static final int isStatic = 16844125; // 0x101055d
     field public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
     field public static final int isolatedSplits = 16844109; // 0x101054d
@@ -6593,7 +6594,7 @@
 
   public static class AssistStructure.ViewNode {
     method public float getAlpha();
-    method public int getAutoFillHint();
+    method public java.lang.String[] getAutoFillHint();
     method public android.view.autofill.AutofillId getAutofillId();
     method public java.lang.String[] getAutofillOptions();
     method public int getAutofillType();
@@ -25294,8 +25295,8 @@
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
-    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
+    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback);
     method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
     method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
     method public deprecated void setNetworkPreference(int);
@@ -36940,11 +36941,11 @@
   }
 
   public static final class SaveInfo.Builder {
-    ctor public SaveInfo.Builder(int);
-    method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutofillId...);
+    ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
     method public android.service.autofill.SaveInfo build();
     method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
     method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender);
+    method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
   }
 
 }
@@ -39367,6 +39368,7 @@
     method public android.os.PersistableBundle getConfigForSubId(int);
     method public void notifyConfigChangedForSubId(int);
     field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
+    field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
     field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
     field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
     field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
@@ -39409,6 +39411,8 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
     field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
+    field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+    field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
     field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
     field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
@@ -39464,6 +39468,7 @@
     field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName";
     field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
     field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent";
+    field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
     field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
     field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
     field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
@@ -45458,7 +45463,7 @@
     method public float getAlpha();
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
-    method public int getAutofillHint();
+    method public java.lang.String[] getAutofillHint();
     method public int getAutofillMode();
     method public int getAutofillType();
     method public android.view.autofill.AutofillValue getAutofillValue();
@@ -45782,7 +45787,7 @@
     method public void setActivated(boolean);
     method public void setAlpha(float);
     method public void setAnimation(android.view.animation.Animation);
-    method public void setAutofillHint(int);
+    method public void setAutofillHint(java.lang.String[]);
     method public void setAutofillMode(int);
     method public void setBackground(android.graphics.drawable.Drawable);
     method public void setBackgroundColor(int);
@@ -45925,20 +45930,19 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
-    field public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
-    field public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 1; // 0x1
-    field public static final int AUTOFILL_HINT_NAME = 2; // 0x2
-    field public static final int AUTOFILL_HINT_NONE = 0; // 0x0
-    field public static final int AUTOFILL_HINT_PASSWORD = 8; // 0x8
-    field public static final int AUTOFILL_HINT_PHONE = 16; // 0x10
-    field public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 32; // 0x20
-    field public static final int AUTOFILL_HINT_POSTAL_CODE = 64; // 0x40
-    field public static final int AUTOFILL_HINT_USERNAME = 4; // 0x4
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
+    field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
+    field public static final java.lang.String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
+    field public static final java.lang.String AUTOFILL_HINT_NAME = "name";
+    field public static final java.lang.String AUTOFILL_HINT_PASSWORD = "password";
+    field public static final java.lang.String AUTOFILL_HINT_PHONE = "phone";
+    field public static final java.lang.String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
+    field public static final java.lang.String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+    field public static final java.lang.String AUTOFILL_HINT_USERNAME = "username";
     field public static final int AUTOFILL_MODE_AUTO = 1; // 0x1
     field public static final int AUTOFILL_MODE_INHERIT = 0; // 0x0
     field public static final int AUTOFILL_MODE_MANUAL = 2; // 0x2
@@ -46604,7 +46608,7 @@
     method public abstract void setAccessibilityFocused(boolean);
     method public abstract void setActivated(boolean);
     method public abstract void setAlpha(float);
-    method public abstract void setAutofillHint(int);
+    method public abstract void setAutofillHint(java.lang.String[]);
     method public abstract void setAutofillOptions(java.lang.String[]);
     method public abstract void setAutofillType(int);
     method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
@@ -46615,6 +46619,7 @@
     method public abstract void setClickable(boolean);
     method public abstract void setContentDescription(java.lang.CharSequence);
     method public abstract void setContextClickable(boolean);
+    method public abstract void setDataIsSensitive(boolean);
     method public abstract void setDimens(int, int, int, int, int, int);
     method public abstract void setElevation(float);
     method public abstract void setEnabled(boolean);
@@ -46625,7 +46630,6 @@
     method public abstract void setInputType(int);
     method public abstract void setLongClickable(boolean);
     method public abstract void setOpaque(boolean);
-    method public abstract void setSanitized(boolean);
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
     method public abstract void setText(java.lang.CharSequence, int, int);
@@ -47902,6 +47906,10 @@
     method public int getListValue();
     method public java.lang.CharSequence getTextValue();
     method public boolean getToggleValue();
+    method public boolean isDate();
+    method public boolean isList();
+    method public boolean isText();
+    method public boolean isToggle();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR;
   }
diff --git a/cmds/idmap/Android.mk b/cmds/idmap/Android.mk
index 50ccb07..aeb8a0c 100644
--- a/cmds/idmap/Android.mk
+++ b/cmds/idmap/Android.mk
@@ -17,7 +17,7 @@
 
 LOCAL_SRC_FILES := idmap.cpp create.cpp scan.cpp inspect.cpp
 
-LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw
+LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw libcutils
 
 LOCAL_MODULE := idmap
 
diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp
index 3ab1915..3a237ff 100644
--- a/cmds/idmap/idmap.cpp
+++ b/cmds/idmap/idmap.cpp
@@ -49,8 +49,8 @@
       --path: create idmap for target package 'target' (path to apk) and overlay package \n\
               'overlay' (path to apk); write results to 'idmap' (path). \n\
 \n\
-      --scan: non-recursively search directory 'dir-to-scan' (path) for overlay packages with \n\
-              target package 'target-package-name-to-look-for' (package name) present at\n\
+      --scan: non-recursively search directory 'dir-to-scan' (path) for static overlay packages \n\
+              with target package 'target-package-name-to-look-for' (package name) present at\n\
               'path-to-target-apk' (path to apk). For each overlay package found, create an\n\
               idmap file in 'dir-to-hold-idmaps' (path). \n\
 \n\
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
index 8122395..67874a8 100644
--- a/cmds/idmap/scan.cpp
+++ b/cmds/idmap/scan.cpp
@@ -9,6 +9,7 @@
 #include <androidfw/ResourceTypes.h>
 #include <androidfw/StreamingZipInflater.h>
 #include <androidfw/ZipFileRO.h>
+#include <cutils/jstring.h>
 #include <private/android_filesystem_config.h> // for AID_SYSTEM
 #include <utils/SortedVector.h>
 #include <utils/String16.h>
@@ -81,7 +82,8 @@
         return String8(tmp);
     }
 
-    int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name)
+    int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name,
+            bool* is_static_overlay)
     {
         const size_t N = parser.getAttributeCount();
         String16 target;
@@ -102,6 +104,11 @@
                         return -1;
                     }
                 }
+            } else if (key == String16("isStatic")) {
+                Res_value v;
+                if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) {
+                    *is_static_overlay = (v.data != 0);
+                }
             }
         }
         if (target == String16(target_package_name)) {
@@ -110,6 +117,28 @@
         return NO_OVERLAY_TAG;
     }
 
+    String16 parse_package_name(const ResXMLTree& parser)
+    {
+        const size_t N = parser.getAttributeCount();
+        String16 package_name;
+        for (size_t i = 0; i < N; ++i) {
+            size_t len;
+            String16 key(parser.getAttributeName(i, &len));
+            if (key == String16("package")) {
+                const char16_t *p = parser.getAttributeStringValue(i, &len);
+                if (p != NULL) {
+                    package_name = String16(p, len);
+                }
+            }
+        }
+        return package_name;
+    }
+
+    bool isValidStaticOverlayPackage(const String16& package_name) {
+        // TODO(b/35742444): Need to support selection method based on a package name.
+        return package_name.size() > 0;
+    }
+
     int parse_manifest(const void *data, size_t size, const char *target_package_name)
     {
         ResXMLTree parser;
@@ -120,17 +149,26 @@
         }
 
         ResXMLParser::event_code_t type;
+        String16 package_name;
+        bool is_static_overlay = false;
+        int priority = NO_OVERLAY_TAG;
         do {
             type = parser.next();
             if (type == ResXMLParser::START_TAG) {
                 size_t len;
                 String16 tag(parser.getElementName(&len));
-                if (tag == String16("overlay")) {
-                    return parse_overlay_tag(parser, target_package_name);
+                if (tag == String16("manifest")) {
+                    package_name = parse_package_name(parser);
+                } else if (tag == String16("overlay")) {
+                    priority = parse_overlay_tag(parser, target_package_name, &is_static_overlay);
+                    break;
                 }
             }
         } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT);
 
+        if (is_static_overlay && isValidStaticOverlayPackage(package_name)) {
+            return priority;
+        }
         return NO_OVERLAY_TAG;
     }
 
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index a512350..21a7ca7 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -28,6 +28,7 @@
 import android.transition.TransitionSet;
 import android.transition.Visibility;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.view.GhostView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -394,6 +395,60 @@
         return transition;
     }
 
+    /**
+     * Looks through the transition to see which Views have been included and which have been
+     * excluded. {@code views} will be modified to contain only those Views that are included
+     * in the transition. If {@code transition} is a TransitionSet, it will search through all
+     * contained Transitions to find targeted Views.
+     *
+     * @param transition The transition to look through for inclusion of Views
+     * @param views The list of Views that are to be checked for inclusion. Will be modified
+     *              to remove all excluded Views, possibly leaving an empty list.
+     */
+    protected static void removeExcludedViews(Transition transition, ArrayList<View> views) {
+        ArraySet<View> included = new ArraySet<>();
+        findIncludedViews(transition, views, included);
+        views.clear();
+        views.addAll(included);
+    }
+
+    /**
+     * Looks through the transition to see which Views have been included. Only {@code views}
+     * will be examined for inclusion. If {@code transition} is a TransitionSet, it will search
+     * through all contained Transitions to find targeted Views.
+     *
+     * @param transition The transition to look through for inclusion of Views
+     * @param views The list of Views that are to be checked for inclusion.
+     * @param included Modified to contain all Views in views that have at least one Transition
+     *                 that affects it.
+     */
+    private static void findIncludedViews(Transition transition, ArrayList<View> views,
+            ArraySet<View> included) {
+        if (transition instanceof TransitionSet) {
+            TransitionSet set = (TransitionSet) transition;
+            ArrayList<View> includedViews = new ArrayList<>();
+            final int numViews = views.size();
+            for (int i = 0; i < numViews; i++) {
+                final View view = views.get(i);
+                if (transition.isValidTarget(view)) {
+                    includedViews.add(view);
+                }
+            }
+            final int count = set.getTransitionCount();
+            for (int i = 0; i < count; i++) {
+                findIncludedViews(set.getTransitionAt(i), includedViews, included);
+            }
+        } else {
+            final int numViews = views.size();
+            for (int i = 0; i < numViews; i++) {
+                final View view = views.get(i);
+                if (transition.isValidTarget(view)) {
+                    included.add(view);
+                }
+            }
+        }
+    }
+
     protected static Transition mergeTransitions(Transition transition1, Transition transition2) {
         if (transition1 == null) {
             return transition2;
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 445b687..ab847fd 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -132,7 +132,9 @@
         super.viewsReady(sharedElements);
         mIsReadyForTransition = true;
         hideViews(mSharedElements);
-        if (getViewsTransition() != null && mTransitioningViews != null) {
+        Transition viewsTransition = getViewsTransition();
+        if (viewsTransition != null && mTransitioningViews != null) {
+            removeExcludedViews(viewsTransition, mTransitioningViews);
             stripOffscreenViews();
             hideViews(mTransitioningViews);
         }
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 29e10d8..df31da9 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -321,6 +321,10 @@
         Transition viewsTransition = null;
         if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
             viewsTransition = configureTransition(getViewsTransition(), true);
+            removeExcludedViews(viewsTransition, mTransitioningViews);
+            if (mTransitioningViews.isEmpty()) {
+                viewsTransition = null;
+            }
         }
         if (viewsTransition == null) {
             viewsTransitionComplete();
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index b219f2a..8a4f8a6 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -164,6 +164,15 @@
     @Override
     public void onClick(DialogInterface dialog, int which) {
         switch (which) {
+            case BUTTON_POSITIVE:
+                // Note this skips input validation and just uses the last valid time and hour
+                // entry. This will only be invoked programmatically. User clicks on BUTTON_POSITIVE
+                // are handled in show().
+                if (mTimeSetListener != null) {
+                    mTimeSetListener.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
+                            mTimePicker.getCurrentMinute());
+                }
+                break;
             case BUTTON_NEGATIVE:
                 cancel();
                 break;
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 1f2ed00..b1fbc8f 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,6 @@
 package android.app.assist;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.ComponentName;
 import android.graphics.Matrix;
@@ -590,7 +591,7 @@
         // fields (viewId and childId) of the field.
         AutofillId mAutofillId;
         @View.AutofillType int mAutofillType;
-        @View.AutofillHint int mAutofillHint;
+        @Nullable String[] mAutofillHint;
         AutofillValue mAutofillValue;
         String[] mAutofillOptions;
         boolean mSanitized;
@@ -676,7 +677,7 @@
                 mSanitized = in.readInt() == 1;
                 mAutofillId = in.readParcelable(null);
                 mAutofillType = in.readInt();
-                mAutofillHint = in.readInt();
+                mAutofillHint = in.readStringArray();
                 mAutofillValue = in.readParcelable(null);
                 mAutofillOptions = in.readStringArray();
             }
@@ -810,7 +811,7 @@
                 out.writeInt(mSanitized ? 1 : 0);
                 out.writeParcelable(mAutofillId, 0);
                 out.writeInt(mAutofillType);
-                out.writeInt(mAutofillHint);
+                out.writeStringArray(mAutofillHint);
                 final AutofillValue sanitizedValue = writeSensitive ? mAutofillValue : null;
                 out.writeParcelable(sanitizedValue,  0);
                 out.writeStringArray(mAutofillOptions);
@@ -949,7 +950,7 @@
          *
          * @return The hint for this view
          */
-        @View.AutofillHint public int getAutoFillHint() {
+        @Nullable public String[] getAutoFillHint() {
             return mAutofillHint;
         }
 
@@ -1012,9 +1013,8 @@
             mAutofillValue = value;
             // TODO(b/33197203, b/33802548): decide whether to set text as well (so it would work
             // with "legacy" views) or just the autofill value
-            final CharSequence text = value.getTextValue();
-            if (text != null) {
-                mText.mText = text;
+            if (value.isText()) {
+                mText.mText = value.getTextValue();
             }
         }
 
@@ -1663,7 +1663,7 @@
         }
 
         @Override
-        public void setAutofillHint(@View.AutofillHint int hint) {
+        public void setAutofillHint(@Nullable String[] hint) {
             mNode.mAutofillHint = hint;
         }
 
@@ -1683,8 +1683,8 @@
         }
 
         @Override
-        public void setSanitized(boolean sanitized) {
-            mNode.mSanitized = sanitized;
+        public void setDataIsSensitive(boolean sensitive) {
+            mNode.mSanitized = !sensitive;
         }
 
         @Override
@@ -1812,7 +1812,7 @@
                     + ", type=" + node.getAutofillType()
                     + ", options=" + Arrays.toString(node.getAutofillOptions())
                     + ", inputType=" + node.getInputType()
-                    + ", hint=" + Integer.toHexString(node.getAutoFillHint())
+                    + ", hint=" + Arrays.toString(node.getAutoFillHint())
                     + ", value=" + node.getAutofillValue()
                     + ", sanitized=" + node.isSanitized());
         }
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
index 8276229..b808c2b 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -81,9 +81,9 @@
     /**
      * Return the free space on the requested storage volume.
      * <p>
-     * The free space is equivalent to {@link File#getFreeSpace()} plus the size
-     * of any cached data that can be automatically deleted by the system as
-     * additional space is needed.
+     * The free space is equivalent to {@link File#getUsableSpace()} plus the
+     * size of any cached data that can be automatically deleted by the system
+     * as additional space is needed.
      * <p>
      * This method may take several seconds to calculate the requested values,
      * so it should only be called from a worker thread.
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 5d5696b..8ff2f35 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -271,6 +271,9 @@
      */
     public String overlayTarget;
 
+    /** @hide */
+    public boolean isStaticOverlay;
+
     public PackageInfo() {
     }
 
@@ -323,6 +326,7 @@
         dest.writeString(restrictedAccountType);
         dest.writeString(requiredAccountType);
         dest.writeString(overlayTarget);
+        dest.writeInt(isStaticOverlay ? 1 : 0);
     }
 
     public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -372,6 +376,7 @@
         restrictedAccountType = source.readString();
         requiredAccountType = source.readString();
         overlayTarget = source.readString();
+        isStaticOverlay = source.readInt() != 0;
 
         // The component lists were flattened with the redundant ApplicationInfo
         // instances omitted.  Distribute the canonical one here as appropriate.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index a1c325a..e15a0e2 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -603,6 +603,7 @@
         pi.restrictedAccountType = p.mRestrictedAccountType;
         pi.requiredAccountType = p.mRequiredAccountType;
         pi.overlayTarget = p.mOverlayTarget;
+        pi.isStaticOverlay = p.mIsStaticOverlay;
         pi.firstInstallTime = firstInstallTime;
         pi.lastUpdateTime = lastUpdateTime;
         if ((flags&PackageManager.GET_GIDS) != 0) {
@@ -2097,6 +2098,9 @@
                         com.android.internal.R.styleable.AndroidManifestResourceOverlay);
                 pkg.mOverlayTarget = sa.getString(
                         com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage);
+                pkg.mIsStaticOverlay = sa.getBoolean(
+                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic,
+                        false);
                 sa.recycle();
 
                 if (pkg.mOverlayTarget == null) {
@@ -2104,6 +2108,9 @@
                     mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                     return null;
                 }
+                if (pkg.mIsStaticOverlay) {
+                    // TODO(b/35742444): Need to support selection method based on a package name.
+                }
                 XmlUtils.skipCurrentTag(parser);
 
             } else if (tagName.equals(TAG_KEY_SETS)) {
@@ -5580,6 +5587,7 @@
         public String mRequiredAccountType;
 
         public String mOverlayTarget;
+        public boolean mIsStaticOverlay;
         public boolean mTrustedOverlay;
 
         /**
@@ -6056,6 +6064,7 @@
             mRestrictedAccountType = dest.readString();
             mRequiredAccountType = dest.readString();
             mOverlayTarget = dest.readString();
+            mIsStaticOverlay = (dest.readInt() == 1);
             mTrustedOverlay = (dest.readInt() == 1);
             mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot);
             mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
@@ -6171,6 +6180,7 @@
             dest.writeString(mRestrictedAccountType);
             dest.writeString(mRequiredAccountType);
             dest.writeString(mOverlayTarget);
+            dest.writeInt(mIsStaticOverlay ? 1 : 0);
             dest.writeInt(mTrustedOverlay ? 1 : 0);
             dest.writeArraySet(mSigningKeys);
             dest.writeArraySet(mUpgradeKeySets);
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index c2795a2a..ad4588f 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -47,23 +47,19 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Flags {}
 
-    public final long lifetime;     // Lifetime of the program in seconds
-    public final int filteredRas;   // Number of RAs filtered by the APF program
-    public final int currentRas;    // Total number of current RAs at generation time
-    public final int programLength; // Length of the APF program in bytes
-    public final int flags;         // Bitfield compound of FLAG_* constants
+    public long lifetime;       // Maximum computed lifetime of the program in seconds
+    public long actualLifetime; // Effective program lifetime in seconds
+    public int filteredRas;     // Number of RAs filtered by the APF program
+    public int currentRas;      // Total number of current RAs at generation time
+    public int programLength;   // Length of the APF program in bytes
+    public int flags;           // Bitfield compound of FLAG_* constants
 
-    public ApfProgramEvent(
-            long lifetime, int filteredRas, int currentRas, int programLength, @Flags int flags) {
-        this.lifetime = lifetime;
-        this.filteredRas = filteredRas;
-        this.currentRas = currentRas;
-        this.programLength = programLength;
-        this.flags = flags;
+    public ApfProgramEvent() {
     }
 
     private ApfProgramEvent(Parcel in) {
         this.lifetime = in.readLong();
+        this.actualLifetime = in.readLong();
         this.filteredRas = in.readInt();
         this.currentRas = in.readInt();
         this.programLength = in.readInt();
@@ -73,6 +69,7 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeLong(lifetime);
+        out.writeLong(actualLifetime);
         out.writeInt(filteredRas);
         out.writeInt(currentRas);
         out.writeInt(programLength);
@@ -87,8 +84,8 @@
     @Override
     public String toString() {
         String lifetimeString = (lifetime < Long.MAX_VALUE) ? lifetime + "s" : "forever";
-        return String.format("ApfProgramEvent(%d/%d RAs %dB %s %s)",
-                filteredRas, currentRas, programLength, lifetimeString, namesOf(flags));
+        return String.format("ApfProgramEvent(%d/%d RAs %dB %ds/%s %s)", filteredRas, currentRas,
+                programLength, actualLifetime, lifetimeString, namesOf(flags));
     }
 
     public static final Parcelable.Creator<ApfProgramEvent> CREATOR
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index f8d7fa9..3b0dc7e 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -25,25 +25,28 @@
  */
 public final class ApfStats implements Parcelable {
 
-    public final long durationMs;     // time interval in milliseconds these stastistics covers
-    public final int receivedRas;     // number of received RAs
-    public final int matchingRas;     // number of received RAs matching a known RA
-    public final int droppedRas;      // number of received RAs ignored due to the MAX_RAS limit
-    public final int zeroLifetimeRas; // number of received RAs with a minimum lifetime of 0
-    public final int parseErrors;     // number of received RAs that could not be parsed
-    public final int programUpdates;  // number of APF program updates
-    public final int maxProgramSize;  // maximum APF program size advertised by hardware
+    /** time interval in milliseconds these stastistics covers. */
+    public long durationMs;
+    /** number of received RAs. */
+    public int receivedRas;
+    /** number of received RAs matching a known RA. */
+    public int matchingRas;
+    /** number of received RAs ignored due to the MAX_RAS limit. */
+    public int droppedRas;
+    /** number of received RAs with a minimum lifetime of 0. */
+    public int zeroLifetimeRas;
+    /** number of received RAs that could not be parsed. */
+    public int parseErrors;
+    /** number of APF program updates from receiving RAs.. */
+    public int programUpdates;
+    /** total number of APF program updates. */
+    public int programUpdatesAll;
+    /** number of APF program updates from allowing multicast traffic. */
+    public int programUpdatesAllowingMulticast;
+    /** maximum APF program size advertised by hardware. */
+    public int maxProgramSize;
 
-    public ApfStats(long durationMs, int receivedRas, int matchingRas, int droppedRas,
-            int zeroLifetimeRas, int parseErrors, int programUpdates, int maxProgramSize) {
-        this.durationMs = durationMs;
-        this.receivedRas = receivedRas;
-        this.matchingRas = matchingRas;
-        this.droppedRas = droppedRas;
-        this.zeroLifetimeRas = zeroLifetimeRas;
-        this.parseErrors = parseErrors;
-        this.programUpdates = programUpdates;
-        this.maxProgramSize = maxProgramSize;
+    public ApfStats() {
     }
 
     private ApfStats(Parcel in) {
@@ -54,6 +57,8 @@
         this.zeroLifetimeRas = in.readInt();
         this.parseErrors = in.readInt();
         this.programUpdates = in.readInt();
+        this.programUpdatesAll = in.readInt();
+        this.programUpdatesAllowingMulticast = in.readInt();
         this.maxProgramSize = in.readInt();
     }
 
@@ -66,6 +71,8 @@
         out.writeInt(zeroLifetimeRas);
         out.writeInt(parseErrors);
         out.writeInt(programUpdates);
+        out.writeInt(programUpdatesAll);
+        out.writeInt(programUpdatesAllowingMulticast);
         out.writeInt(maxProgramSize);
     }
 
@@ -83,8 +90,9 @@
                 .append(String.format("%d matching, ", matchingRas))
                 .append(String.format("%d dropped, ", droppedRas))
                 .append(String.format("%d zero lifetime, ", zeroLifetimeRas))
-                .append(String.format("%d parse errors, ", parseErrors))
-                .append(String.format("%d program updates})", programUpdates))
+                .append(String.format("%d parse errors}, ", parseErrors))
+                .append(String.format("updates: {all: %d, RAs: %d, allow multicast: %d})",
+                        programUpdatesAll, programUpdates, programUpdatesAllowingMulticast))
                 .toString();
     }
 
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 46f2d38..1fc0b82 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -373,8 +373,7 @@
     }
 
     /** {@hide} */
-    // TODO(b/26742218): find out where toString() is called internally and replace these calls by
-    // dump().
+    // TODO: find out where toString() is called internally and replace these calls by dump().
     public String dump() {
         final CharArrayWriter writer = new CharArrayWriter();
         dump(new IndentingPrintWriter(writer, "    ", 80));
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index f53b0d7..56d4ff7 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -1563,7 +1563,7 @@
         if (resolver.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
             if (e instanceof ParcelableException) {
                 ((ParcelableException) e).maybeRethrow(FileNotFoundException.class);
-            } else if (e instanceof RemoteException ) {
+            } else if (e instanceof RemoteException) {
                 ((RemoteException) e).rethrowAsRuntimeException();
             } else if (e instanceof RuntimeException) {
                 throw (RuntimeException) e;
diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java
index d220052..f6d40db 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -78,14 +78,12 @@
             // TODO(b/35956626): inline newSettingsActivity once clients migrate
             final String newSettingsActivity =
                     metaDataArray.getString(R.styleable.AutofillService_settingsActivity);
-            System.out.println(">>> NEW CRAP MAN: " + newSettingsActivity); // TODO(felipeal): tmp
             if (newSettingsActivity != null) {
                 mSettingsActivity = newSettingsActivity;
             } else {
                 mSettingsActivity =
                         metaDataArray.getString(R.styleable.AutoFillService_settingsActivity);
             }
-            System.out.println(">>> FINAL CRAP MAN: " + mSettingsActivity); // TODO(felipeal): tmp
             metaDataArray.recycle();
         } else {
             mSettingsActivity = null;
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 2461947..ebe02c2 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -167,6 +167,7 @@
         public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) {
             return setValue(id.getDaRealId(), value.getDaRealValue());
         }
+
         /**
          * Sets the value of a field.
          *
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 069e83c..b808de7 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -44,8 +44,8 @@
  * <pre class="prettyprint">
  *  new FillResponse.Builder()
  *      .add(new Dataset.Builder(createPresentation())
- *          .setTextFieldValue(id1, "homer")
- *          .setTextFieldValue(id2, "D'OH!")
+ *          .setValue(id1, AutofillValue.forText("homer"))
+ *          .setValue(id2, AutofillValue.forText("D'OH!"))
  *          .build())
  *      .build();
  * </pre>
@@ -55,48 +55,19 @@
  * <pre class="prettyprint">
  *  new FillResponse.Builder()
  *      .add(new Dataset.Builder(createFirstPresentation())
- *          .setTextFieldValue(id1, "homer")
- *          .setTextFieldValue(id2, "D'OH!")
+ *          .setValue(id1, AutofillValue.forText("homer"))
+ *          .setValue(id2, AutofillValue.forText("D'OH!"))
  *          .build())
  *      .add(new Dataset.Builder(createSecondPresentation())
- *          .setTextFieldValue(id1, "elbarto")
- *          .setTextFieldValue(id2, "cowabonga")
+ *          .setValue(id1, AutofillValue.forText("elbarto")
+ *          .setValue(id2, AutofillValue.forText("cowabonga")
  *          .build())
  *      .build();
  * </pre>
  *
- * <p>If the user does not have any data associated with this {@link android.app.Activity} but
- * the service wants to offer the user the option to save the data that was entered, then the
- * service could populate the response with a {@link SaveInfo} instead of {@link Dataset}s:
- *
- * <pre class="prettyprint">
- *  new FillResponse.Builder()
- *      .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_CREDENTIALS)
- *                   .addSavableFields(id1, id2))
- *      .build();
- * </pre>
- *
- * <p>Similarly, there might be cases where the user data on the service is enough to populate some
- * fields but not all, and the service would still be interested on saving the other fields. In this
- * scenario, the service could populate the response with both {@link Dataset}s and
- * {@link SaveInfo}:
- *
- * <pre class="prettyprint">
- *   new FillResponse.Builder()
- *       .add(new Dataset.Builder(createPresentation())
- *          .setTextFieldValue(id1, "Homer")                  // first name
- *          .setTextFieldValue(id2, "Simpson")                // last name
- *          .setTextFieldValue(id3, "742 Evergreen Terrace")  // street
- *          .setTextFieldValue(id4, "Springfield")            // city
- *          .build())
- *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS)
- *                   .addSavableFields(id5, id6)) // state and zipcode
- *       .build();
- *
- * </pre>
- *
- * <p>Notice that the ids that are part of a dataset (ids 1 to 4, in this example) are automatically
- * added to the {@code savableIds} list.
+ * If the service is interested on saving the user-edited data back, it must set a {@link SaveInfo}
+ * in the {@link FillResponse}. Typically, the {@link SaveInfo} contains the same ids as the
+ * {@link Dataset}, but other combinations are possible - see {@link SaveInfo} for more details
  *
  * <p>If the service has multiple {@link Dataset}s for different sections of the activity,
  * for example, a user section for which there are two datasets followed by an address
@@ -113,12 +84,12 @@
  * <pre class="prettyprint">
  *  new FillResponse.Builder()
  *      .add(new Dataset.Builder(createFirstPresentation())
- *          .setTextFieldValue(id1, "Homer")
- *          .setTextFieldValue(id2, "Simpson")
+ *          .setValue(id1, AutofillValue.forText("Homer"))
+ *          .setValue(id2, AutofillValue.forText("Simpson"))
  *          .build())
  *      .add(new Dataset.Builder(createSecondPresentation())
- *          .setTextFieldValue(id1, "Bart")
- *          .setTextFieldValue(id2, "Simpson")
+ *          .setValue(id1, AutofillValue.forText("Bart"))
+ *          .setValue(id2, AutofillValue.forText("Simpson"))
  *          .build())
  *      .build();
  * </pre>
@@ -129,12 +100,12 @@
  * <pre class="prettyprint">
  *  new FillResponse.Builder()
  *      .add(new Dataset.Builder(createThirdPresentation())
- *          .setTextFieldValue(id3, "742 Evergreen Terrace")
- *          .setTextFieldValue(id4, "Springfield")
+ *          .setValue(id3, AutofillValue.forText("742 Evergreen Terrace"))
+ *          .setValue(id4, AutofillValue.forText("Springfield"))
  *          .build())
  *      .add(new Dataset.Builder(createFourthPresentation())
- *          .setTextFieldValue(id3, "Springfield Power Plant")
- *          .setTextFieldValue(id4, "Springfield")
+ *          .setValue(id3, AutofillValue.forText("Springfield Power Plant"))
+ *          .setValue(id4, AutofillValue.forText("Springfield"))
  *          .build())
  *      .build();
  * </pre>
@@ -167,16 +138,7 @@
 
     private FillResponse(@NonNull Builder builder) {
         mDatasets = builder.mDatasets;
-
         mSaveInfo = builder.mSaveInfo;
-        if (mSaveInfo != null) {
-            mSaveInfo.addSavableIds(mDatasets);
-            if (mSaveInfo.getSavableIds() == null) {
-                throw new IllegalArgumentException(
-                        "need to provide at least one savable id on SaveInfo");
-            }
-        }
-
         mExtras = builder.mExtras;
         mPresentation = builder.mPresentation;
         mAuthentication = builder.mAuthentication;
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 1bd88c7..6d368b5 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -25,28 +25,86 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.ArraySet;
 import android.view.autofill.AutoFillId;
 import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
- * Information used to indicate that a service is interested on saving the user-inputed data for
- * future use.
+ * Information used to indicate that an {@link AutofillService} is interested on saving the
+ * user-inputed data for future use, through a
+ * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, SaveCallback)}
+ * call.
  *
- * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}.
+ * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least
+ * two pieces of information:
  *
- * <p>A {@link SaveInfo} must define the type it represents, and contain at least one
- * {@code savableId}. A {@code savableId} is the {@link AutofillId} of a view the service is
- * interested to save in a {@code onSaveRequest()}; the ids of all {@link Dataset} present in the
- * {@link FillResponse} associated with this {@link SaveInfo} are already marked as savable,
- * but additional ids can be added through {@link Builder#addSavableIds(AutofillId...)}.
+ * <ol>
+ *   <li>The type of user data that would be saved (like passoword or credit card info).
+ *   <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed
+ *       to trigger a save request.
+ * </ol>
  *
- * <p>See {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * SaveCallback)} and {@link FillResponse} for more info.
+ *  Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}:
+ *
+ * <pre class="prettyprint">
+ *  new FillResponse.Builder()
+ *      .add(new Dataset.Builder(createPresentation())
+ *          .setValue(id1, AutofillValue.forText("homer"))
+ *          .setValue(id2, AutofillValue.forText("D'OH!"))
+ *          .build())
+ *      .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2})
+ *                  .build())
+ *      .build();
+ * </pre>
+ *
+ * There might be cases where the {@link AutofillService} knows how to fill the
+ * {@link android.app.Activity}, but the user has no data for it. In that case, the
+ * {@link FillResponse} should contain just the {@link SaveInfo}, but no {@link Dataset}s:
+ *
+ * <pre class="prettyprint">
+ *  new FillResponse.Builder()
+ *      .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2})
+ *                  .build())
+ *      .build();
+ * </pre>
+ *
+ * <p>There might be cases where the user data in the {@link AutofillService} is enough
+ * to populate some fields but not all, and the service would still be interested on saving the
+ * other fields. In this scenario, the service could set the
+ * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well:
+ *
+ * <pre class="prettyprint">
+ *   new FillResponse.Builder()
+ *       .add(new Dataset.Builder(createPresentation())
+ *          .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"))  // street
+ *          .setValue(id2, AutofillValue.forText("Springfield"))            // city
+ *          .build())
+ *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS, new int[] {id1, id2})
+ *                   .setOptionalIds(new int[] {id3, id4}) // state and zipcode
+ *                   .build())
+ *       .build();
+ * </pre>
+ *
+ * The
+ * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, SaveCallback)}
+ * is triggered after a call to {@link AutofillManager#commit()}, but only when all conditions
+ * below are met:
+ *
+ * <ol>
+ *   <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}.
+ *   <li>The {@link AutofillValue} of all required views (as set by the {@code requiredIds} passed
+ *       to {@link SaveInfo.Builder} constructor are not empty.
+ *   <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed
+ *       (i.e., it's not the same value passed in a {@link Dataset}).
+ *   <li>The user explicitly tapped the affordance asking to save data for autofill.
+ * </ol>
  */
 public final class SaveInfo implements Parcelable {
 
@@ -74,9 +132,10 @@
     public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3;
 
     private final @SaveDataType int mType;
-    private CharSequence mNegativeActionTitle;
-    private IntentSender mNegativeActionListener;
-    private ArraySet<AutofillId> mSavableIds;
+    private final CharSequence mNegativeActionTitle;
+    private final IntentSender mNegativeActionListener;
+    private final AutofillId[] mRequiredIds;
+    private final AutofillId[] mOptionalIds;
     private final CharSequence mDescription;
 
     /** @hide */
@@ -94,7 +153,8 @@
         mType = builder.mType;
         mNegativeActionTitle = builder.mNegativeActionTitle;
         mNegativeActionListener = builder.mNegativeActionListener;
-        mSavableIds = builder.mSavableIds;
+        mRequiredIds = builder.mRequiredIds;
+        mOptionalIds = builder.mOptionalIds;
         mDescription = builder.mDescription;
     }
 
@@ -109,8 +169,13 @@
     }
 
     /** @hide */
-    public @Nullable ArraySet<AutofillId> getSavableIds() {
-        return mSavableIds;
+    public AutofillId[] getRequiredIds() {
+        return mRequiredIds;
+    }
+
+    /** @hide */
+    public @Nullable AutofillId[] getOptionalIds() {
+        return mOptionalIds;
     }
 
     /** @hide */
@@ -123,25 +188,6 @@
         return mDescription;
     }
 
-    /** @hide */
-    public void addSavableIds(@Nullable ArrayList<Dataset> datasets) {
-        if (datasets != null) {
-            for (Dataset dataset : datasets) {
-                final ArrayList<AutofillId> ids = dataset.getFieldIds();
-                if (ids != null) {
-                    final int fieldCount = ids.size();
-                    for (int i = 0; i < fieldCount; i++) {
-                        final AutofillId id = ids.get(i);
-                        if (mSavableIds == null) {
-                            mSavableIds = new ArraySet<>();
-                        }
-                        mSavableIds.add(id);
-                    }
-                }
-            }
-        }
-    }
-
     /**
      * A builder for {@link SaveInfo} objects.
      */
@@ -150,7 +196,9 @@
         private final @SaveDataType int mType;
         private CharSequence mNegativeActionTitle;
         private IntentSender mNegativeActionListener;
-        private ArraySet<AutofillId> mSavableIds;
+        // TODO(b/33197203): make mRequiredIds final once addSavableIds() is gone
+        private AutofillId[] mRequiredIds;
+        private AutofillId[] mOptionalIds;
         private CharSequence mDescription;
         private boolean mDestroyed;
 
@@ -161,8 +209,15 @@
          * be {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
          * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, or {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD};
          * otherwise it will assume {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}.
+         * @param requiredIds ids of all required views that will trigger a save request.
+         *
+         * <p>See {@link SaveInfo} for more info.
+         *
+         * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty.
          */
-        public Builder(@SaveDataType int type) {
+        public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
+            Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0,
+                    "must have at least on required id: " + Arrays.toString(requiredIds));
             switch (type) {
                 case SAVE_DATA_TYPE_PASSWORD:
                 case SAVE_DATA_TYPE_ADDRESS:
@@ -172,28 +227,43 @@
                 default:
                     mType = SAVE_DATA_TYPE_GENERIC;
             }
+            mRequiredIds = requiredIds;
         }
 
         /**
-         * Adds ids of additional views the service would be interested to save, but were not
-         * indirectly set through {@link FillResponse.Builder#addDataset(Dataset)}.
-         *
-         * @param ids The savable ids.
-         * @return This builder.
-         *
-         * @see FillResponse
+         * @hide
+         * @deprecated
+         * // TODO(b/33197203): make sure is removed when clients migrated
          */
+        @Deprecated
+        public Builder(@SaveDataType int type) {
+            this(type, null);
+        }
+
+        /**
+         * @hide
+         * @deprecated
+         * // TODO(b/33197203): make sure is removed when clients migrated
+         */
+        @Deprecated
         public @NonNull Builder addSavableIds(@Nullable AutofillId... ids) {
             throwIfDestroyed();
+            mRequiredIds = ids;
+            return this;
+        }
 
-            if (ids == null) {
-                return this;
-            }
-            for (AutofillId id : ids) {
-                if (mSavableIds == null) {
-                    mSavableIds = new ArraySet<>();
-                }
-                mSavableIds.add(id);
+        /**
+         * Sets the ids of additional, optional views the service would be interested to save.
+         *
+         * <p>See {@link SaveInfo} for more info.
+         *
+         * @param ids The ids of the optional views.
+         * @return This builder.
+         */
+        public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) {
+            throwIfDestroyed();
+            if (ids != null && ids.length != 0) {
+                mOptionalIds = ids;
             }
             return this;
         }
@@ -206,14 +276,14 @@
         public @NonNull Builder addSavableIds(@Nullable AutoFillId... ids) {
             throwIfDestroyed();
 
-            if (ids == null) {
+            if (ids == null || ids.length == 0) {
                 return this;
             }
-            for (AutoFillId id : ids) {
-                if (mSavableIds == null) {
-                    mSavableIds = new ArraySet<>();
-                }
-                mSavableIds.add(id.getDaRealId());
+            if (mRequiredIds == null) {
+                mRequiredIds = new AutofillId[ids.length];
+            }
+            for (int i = 0; i < ids.length; i++) {
+                mRequiredIds[i] = ids[i].getDaRealId();
             }
             return this;
         }
@@ -228,6 +298,7 @@
          * @return This Builder.
          */
         public @NonNull Builder setDescription(@Nullable CharSequence description) {
+            throwIfDestroyed();
             mDescription = description;
             return this;
         }
@@ -293,7 +364,9 @@
         if (!DEBUG) return super.toString();
 
         return new StringBuilder("SaveInfo: [type=").append(mType)
-                .append(", savableIds=").append(mSavableIds)
+                .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
+                .append(", optionalIds=").append(Arrays.toString(mOptionalIds))
+                .append(", description=").append(mDescription)
                 .append("]").toString();
     }
 
@@ -309,9 +382,10 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(mType);
+        parcel.writeParcelableArray(mRequiredIds, flags);
         parcel.writeCharSequence(mNegativeActionTitle);
         parcel.writeParcelable(mNegativeActionListener, flags);
-        parcel.writeTypedArraySet(mSavableIds, flags);
+        parcel.writeParcelableArray(mOptionalIds, flags);
         parcel.writeCharSequence(mDescription);
     }
 
@@ -321,13 +395,10 @@
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
-            final Builder builder = new Builder(parcel.readInt());
+            final Builder builder = new Builder(parcel.readInt(),
+                    parcel.readParcelableArray(null, AutofillId.class));
             builder.setNegativeAction(parcel.readCharSequence(), parcel.readParcelable(null));
-            final ArraySet<AutofillId> savableIds = parcel.readTypedArraySet(null);
-            final int savableIdsCount = (savableIds != null) ? savableIds.size() : 0;
-            for (int i = 0; i < savableIdsCount; i++) {
-                builder.addSavableIds(savableIds.valueAt(i));
-            }
+            builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class));
             builder.setDescription(parcel.readCharSequence());
             return builder.build();
         }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 94c463c..353dfed 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -1031,6 +1031,7 @@
                                    float avail, TextUtils.TruncateAt where,
                                    int line, float textWidth, TextPaint paint,
                                    boolean forceEllipsis) {
+        avail -= getTotalInsets(line);
         if (textWidth <= avail && !forceEllipsis) {
             // Everything fits!
             mLines[mColumns * line + ELLIPSIS_START] = 0;
@@ -1134,6 +1135,17 @@
         mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
     }
 
+    private float getTotalInsets(int line) {
+        int totalIndent = 0;
+        if (mLeftIndents != null) {
+            totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
+        }
+        if (mRightIndents != null) {
+            totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)];
+        }
+        return totalIndent;
+    }
+
     // Override the base class so we can directly access our members,
     // rather than relying on member functions.
     // The logic mirrors that of Layout.getLineForVertical
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index af2547e..255a029 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -799,8 +799,10 @@
      * targetId list. If the target parameter is null, then the target list
      * is not checked (this is in the case of ListView items, where the
      * views are ignored and only the ids are used).
+     *
+     * @hide
      */
-    boolean isValidTarget(View target) {
+    public boolean isValidTarget(View target) {
         if (target == null) {
             return false;
         }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 029caf9..80f6c32 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -978,142 +978,123 @@
      */
     public static final int AUTOFILL_MODE_MANUAL = 2;
 
-    /** @hide */
-    @IntDef({
-            AUTOFILL_HINT_NONE,
-            AUTOFILL_HINT_EMAIL_ADDRESS,
-            AUTOFILL_HINT_NAME,
-            AUTOFILL_HINT_POSTAL_ADDRESS,
-            AUTOFILL_HINT_PASSWORD,
-            AUTOFILL_HINT_PHONE,
-            AUTOFILL_HINT_USERNAME,
-            AUTOFILL_HINT_POSTAL_CODE,
-            AUTOFILL_HINT_CREDIT_CARD_NUMBER,
-            AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE,
-            AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE,
-            AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH,
-            AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR,
-            AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface AutofillHint {}
-
-    /**
-     * No autofill hint is set.
-     *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
-     */
-    public static final int AUTOFILL_HINT_NONE = 0;
-
     /**
      * This view contains an email address.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_EMAIL_ADDRESS}"
+     * to <a href="#attr_android:autofillHint"> {@code android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 0x1;
+    public static final String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
 
     /**
      * The view contains a real name.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_NAME}" to
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_NAME = 0x2;
+    public static final String AUTOFILL_HINT_NAME = "name";
 
     /**
      * The view contains a user name.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_USERNAME}" to
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_USERNAME = 0x4;
+    public static final String AUTOFILL_HINT_USERNAME = "username";
 
     /**
      * The view contains a password.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_PASSWORD}" to
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_PASSWORD = 0x8;
+    public static final String AUTOFILL_HINT_PASSWORD = "password";
 
     /**
      * The view contains a phone number.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_PHONE}" to
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_PHONE = 0x10;
+    public static final String AUTOFILL_HINT_PHONE = "phone";
 
     /**
      * The view contains a postal address.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_POSTAL_ADDRESS}"
+     * to <a href="#attr_android:autofillHint"> {@code android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 0x20;
+    public static final String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
 
     /**
      * The view contains a postal code.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_POSTAL_CODE}" to
+     * <a href="#attr_android:autofillHint"> {@code android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_POSTAL_CODE = 0x40;
+    public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
 
     /**
      * The view contains a credit card number.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value
+     * #AUTOFILL_HINT_CREDIT_CARD_NUMBER}" to <a href="#attr_android:autofillHint"> {@code
+     * android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 0x80;
+    public static final String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
 
     /**
      * The view contains a credit card security code.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value
+     * #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE}" to <a href="#attr_android:autofillHint"> {@code
+     * android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 0x100;
+    public static final String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
 
     /**
      * The view contains a credit card expiration date.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value
+     * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}" to <a href="#attr_android:autofillHint"> {@code
+     * android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 0x200;
+    public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE =
+            "creditCardExpirationDate";
 
     /**
      * The view contains the month a credit card expires.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value
+     * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}" to <a href="#attr_android:autofillHint"> {@code
+     * android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 0x400;
+    public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH =
+            "creditCardExpirationMonth";
 
     /**
      * The view contains the year a credit card expires.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value
+     * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}" to <a href="#attr_android:autofillHint"> {@code
+     * android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 0x800;
+    public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR =
+            "creditCardExpirationYear";
 
     /**
      * The view contains the day a credit card expires.
      *
-     * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint">
-     * {@code android:autofillHint}.
+     * Use with {@link #setAutofillHint(String[])}, or set "{@value
+     * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY}" to <a href="#attr_android:autofillHint"> {@code
+     * android:autofillHint}.
      */
-    public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 0x1000;
+    public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay";
 
     /**
-     * Hint for the autofill services that describes the content of the view.
+     * Hintd for the autofill services that describes the content of the view.
      */
-    @AutofillHint private int mAutofillHint;
+    private @Nullable String[] mAutofillHint;
 
     /** @hide */
     @IntDef({
@@ -5049,7 +5030,37 @@
                     break;
                 case R.styleable.View_autofillHint:
                     if (a.peekValue(attr) != null) {
-                        setAutofillHint(a.getInt(attr, AUTOFILL_HINT_NONE));
+                        CharSequence[] rawHints = null;
+                        String rawString = null;
+
+                        if (a.getType(attr) == TypedValue.TYPE_REFERENCE) {
+                            int resId = a.getResourceId(attr, 0);
+
+                            try {
+                                rawHints = a.getTextArray(attr);
+                            } catch (NullPointerException e) {
+                                rawString = getResources().getString(resId);
+                            }
+                        } else {
+                            rawString = a.getString(attr);
+                        }
+
+                        if (rawHints == null) {
+                            if (rawString == null) {
+                                throw new IllegalArgumentException(
+                                        "Could not resolve autofillHint");
+                            } else {
+                                rawHints = rawString.split(",");
+                            }
+                        }
+
+                        String[] hints = new String[rawHints.length];
+
+                        int numHints = rawHints.length;
+                        for (int rawHintNum = 0; rawHintNum < numHints; rawHintNum++) {
+                            hints[rawHintNum] = rawHints[rawHintNum].toString().trim();
+                        }
+                        setAutofillHint(hints);
                     }
                     break;
                 case R.styleable.View_importantForAutofill:
@@ -7257,9 +7268,10 @@
      * Called when assist structure is being retrieved from a view as part of an autofill request.
      *
      * <p>This method already provides most of what's needed for autofill, but should be overridden
+     * when:
      * <ol>
      * <li>The view contents does not include PII (Personally Identifiable Information), so it
-     * can call {@link ViewStructure#setSanitized(boolean)} passing {@code true}.
+     * can call {@link ViewStructure#setDataIsSensitive(boolean)} passing {@code false}.
      * <li>It must set fields such {@link ViewStructure#setText(CharSequence)},
      * {@link ViewStructure#setAutofillOptions(String[])}, or {@link ViewStructure#setUrl(String)}.
      * </ol>
@@ -7464,12 +7476,12 @@
     /**
      * Describes the content of a view so that a autofill service can fill in the appropriate data.
      *
-     * @return The hint set via the attribute
+     * @return The hint set via the attribute or {@code null} if no hint it set.
      *
      * @attr ref android.R.styleable#View_autofillHint
      */
     @ViewDebug.ExportedProperty()
-    @AutofillHint public int getAutofillHint() {
+    @Nullable public String[] getAutofillHint() {
         return mAutofillHint;
     }
 
@@ -9099,11 +9111,15 @@
      * Sets the a hint that helps the autofill service to select the appropriate data to fill the
      * view.
      *
-     * @param autofillHint The autofill hint to set
+     * @param autofillHint The autofill hint to set. If the array is emtpy, {@code null} is set.
      * @attr ref android.R.styleable#View_autofillHint
      */
-    public void setAutofillHint(@AutofillHint int autofillHint) {
-        mAutofillHint = autofillHint;
+    public void setAutofillHint(@Nullable String... autofillHint) {
+        if (autofillHint == null || autofillHint.length == 0) {
+            mAutofillHint = null;
+        } else {
+            mAutofillHint = autofillHint;
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index bccaca2..a71b899 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -323,7 +324,7 @@
      * Sets the a hint that helps the autofill service to select the appropriate data to fill the
      * view.
      */
-    public abstract void setAutofillHint(@View.AutofillHint int hint);
+    public abstract void setAutofillHint(@Nullable String[] hint);
 
     /**
      * Sets the {@link AutofillValue} representing the current value of this node.
@@ -346,19 +347,27 @@
     public abstract void setInputType(int inputType);
 
     /**
-     * Marks this node as sanitized so its content are sent on {@link
+     * Sets whether the data on this node is sensitive; if it is, then its content (text, autofill
+     * value, etc..) is striped before calls to {@link
      * android.service.autofill.AutofillService#onFillRequest(android.app.assist.AssistStructure,
      * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)}.
      *
-     * <p>Only nodes that does not have PII (Personally Identifiable Information - sensitive data
-     * such as email addresses, credit card numbers, passwords, etc...) should be marked
-     * as sanitized; a good rule of thumb is to mark as sanitized nodes whose value were statically
-     * set from resources.
+     * <p>By default, all nodes are assumed to be sensitive, and only nodes that does not have PII
+     * (Personally Identifiable Information - sensitive data such as email addresses, credit card
+     * numbers, passwords, etc...) should be marked as non-sensitive; a good rule of thumb is to
+     * mark as non-sensitive nodes whose value were statically set from resources.
+     *
+     * <p>Notice that the content of even sensitive nodes are sent to the service (through the
+     * {@link
+     * android.service.autofill.AutofillService#onSaveRequest(android.app.assist.AssistStructure,
+     * Bundle, android.service.autofill.SaveCallback)} call) when the user consented to save the
+     * data, so it is important to set the content of sensitive nodes as well, but mark them as
+     * sensitive.
      *
      * <p>Should only be set when the node is used for autofill purposes - it will be ignored
      * when used for Assist.
      */
-    public abstract void setSanitized(boolean sanitized);
+    public abstract void setDataIsSensitive(boolean sensitive);
 
     /**
      * Call when done populating a {@link ViewStructure} returned by
diff --git a/core/java/android/view/autofill/AutofillValue.java b/core/java/android/view/autofill/AutofillValue.java
index 0c7620e..9d7e08e 100644
--- a/core/java/android/view/autofill/AutofillValue.java
+++ b/core/java/android/view/autofill/AutofillValue.java
@@ -16,13 +16,23 @@
 
 package android.view.autofill;
 
+import static android.view.View.AUTOFILL_TYPE_DATE;
+import static android.view.View.AUTOFILL_TYPE_LIST;
+import static android.view.View.AUTOFILL_TYPE_TEXT;
+import static android.view.View.AUTOFILL_TYPE_TOGGLE;
 import static android.view.autofill.Helper.DEBUG;
+import static android.view.autofill.Helper.VERBOSE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.View;
 
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
 /**
  * Abstracts how a {@link View} can be autofilled by an
  * {@link android.service.autofill.AutofillService}.
@@ -31,52 +41,107 @@
  * {@link View#getAutofillType()}.
  */
 public final class AutofillValue implements Parcelable {
-    private final String mText;
-    private final int mListIndex;
-    private final boolean mToggle;
-    private final long mDate;
+    private final @View.AutofillType int mType;
+    private final @NonNull Object mValue;
 
-    private AutofillValue(CharSequence text, int listIndex, boolean toggle, long date) {
-        mText = (text == null) ? null : text.toString();
-        mListIndex = listIndex;
-        mToggle = toggle;
-        mDate = date;
+    private AutofillValue(@View.AutofillType int type, @NonNull Object value) {
+        mType = type;
+        mValue = value;
     }
 
     /**
      * Gets the value to autofill a text field.
      *
-     * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.
+     * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a text value
      */
-    public CharSequence getTextValue() {
-        return mText;
+    @NonNull public CharSequence getTextValue() {
+        Preconditions.checkState(isText(), "value must be a text value, not type=" + mType);
+        return (CharSequence) mValue;
+    }
+
+    /**
+     * Checks is this is a text value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p>
+     */
+    public boolean isText() {
+        return mType == AUTOFILL_TYPE_TEXT;
     }
 
     /**
      * Gets the value to autofill a toggable field.
      *
-     * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.
+     * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a toggle value
      */
     public boolean getToggleValue() {
-        return mToggle;
+        Preconditions.checkState(isToggle(), "value must be a toggle value, not type=" + mType);
+        return (Boolean) mValue;
+    }
+
+    /**
+     * Checks is this is a toggle value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p>
+     */
+    public boolean isToggle() {
+        return mType == AUTOFILL_TYPE_TOGGLE;
     }
 
     /**
      * Gets the value to autofill a selection list field.
      *
-     * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.
+     * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a list value
      */
     public int getListValue() {
-        return mListIndex;
+        Preconditions.checkState(isList(), "value must be a list value, not type=" + mType);
+        return (Integer) mValue;
+    }
+
+    /**
+     * Checks is this is a list value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p>
+     */
+    public boolean isList() {
+        return mType == AUTOFILL_TYPE_LIST;
     }
 
     /**
      * Gets the value to autofill a date field.
      *
-     * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.
+     * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a date value
      */
     public long getDateValue() {
-        return mDate;
+        Preconditions.checkState(isDate(), "value must be a date value, not type=" + mType);
+        return (Long) mValue;
+    }
+
+    /**
+     * Checks is this is a date value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p>
+     */
+    public boolean isDate() {
+        return mType == AUTOFILL_TYPE_DATE;
+    }
+
+    /**
+     * Used to define whether a field is empty so it's not sent to service on save.
+     *
+     * <p>Only applies to some types, like text.
+     *
+     * @hide
+     */
+    public boolean isEmpty() {
+        return isText() && ((CharSequence) mValue).length() == 0;
     }
 
     /////////////////////////////////////
@@ -85,13 +150,7 @@
 
     @Override
     public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((mText == null) ? 0 : mText.hashCode());
-        result = prime * result + mListIndex;
-        result = prime * result + (mToggle ? 1231 : 1237);
-        result = prime * result + (int) (mDate ^ (mDate >>> 32));
-        return result;
+        return mType + mValue.hashCode();
     }
 
     @Override
@@ -100,32 +159,30 @@
         if (obj == null) return false;
         if (getClass() != obj.getClass()) return false;
         final AutofillValue other = (AutofillValue) obj;
-        if (mText == null) {
-            if (other.mText != null) return false;
+
+        if (mType != other.mType) return false;
+
+        if (isText()) {
+            return mValue.toString().equals(other.mValue.toString());
         } else {
-            if (!mText.equals(other.mText)) return false;
+            return Objects.equals(mValue, other.mValue);
         }
-        if (mListIndex != other.mListIndex) return false;
-        if (mToggle != other.mToggle) return false;
-        if (mDate != other.mDate) return false;
-        return true;
     }
 
     /** @hide */
     public String coerceToString() {
         // TODO(b/33197203): How can we filter on toggles or list values?
-        return mText;
+        return mValue.toString();
     }
 
     @Override
     public String toString() {
         if (!DEBUG) return super.toString();
 
-        if (mText != null) {
-            return mText.length() + "_chars";
-        }
+        final String sanitizedValue = isText() && !VERBOSE
+                ? ((CharSequence) mValue).length() + "_chars" : mValue.toString();
 
-        return "[l=" + mListIndex + ", t=" + mToggle + ", d=" + mDate + "]";
+        return "[type=" + mType + ", value=" + sanitizedValue + "]";
     }
 
     /////////////////////////////////////
@@ -139,17 +196,44 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeString(mText);
-        parcel.writeInt(mListIndex);
-        parcel.writeInt(mToggle ? 1 : 0);
-        parcel.writeLong(mDate);
+        parcel.writeInt(mType);
+
+        switch (mType) {
+            case AUTOFILL_TYPE_TEXT:
+                parcel.writeCharSequence((CharSequence) mValue);
+                break;
+            case AUTOFILL_TYPE_TOGGLE:
+                parcel.writeInt((Boolean) mValue ? 1 : 0);
+                break;
+            case AUTOFILL_TYPE_LIST:
+                parcel.writeInt((Integer) mValue);
+                break;
+            case AUTOFILL_TYPE_DATE:
+                parcel.writeLong((Long) mValue);
+                break;
+        }
     }
 
-    private AutofillValue(Parcel parcel) {
-        mText = parcel.readString();
-        mListIndex = parcel.readInt();
-        mToggle = parcel.readInt() == 1;
-        mDate = parcel.readLong();
+    private AutofillValue(@NonNull Parcel parcel) {
+        mType = parcel.readInt();
+
+        switch (mType) {
+            case AUTOFILL_TYPE_TEXT:
+                mValue = parcel.readCharSequence();
+                break;
+            case AUTOFILL_TYPE_TOGGLE:
+                int rawValue = parcel.readInt();
+                mValue = rawValue != 0;
+                break;
+            case AUTOFILL_TYPE_LIST:
+                mValue = parcel.readInt();
+                break;
+            case AUTOFILL_TYPE_DATE:
+                mValue = parcel.readLong();
+                break;
+            default:
+                throw new IllegalArgumentException("type=" + mType + " not valid");
+        }
     }
 
     public static final Parcelable.Creator<AutofillValue> CREATOR =
@@ -175,9 +259,8 @@
      * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.
      */
     // TODO(b/33197203): use cache
-    @Nullable
     public static AutofillValue forText(@Nullable CharSequence value) {
-        return value == null ? null : new AutofillValue(value, 0, false, 0);
+        return value == null ? null : new AutofillValue(AUTOFILL_TYPE_TEXT, value);
     }
 
     /**
@@ -187,7 +270,7 @@
      * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.
      */
     public static AutofillValue forToggle(boolean value) {
-        return new AutofillValue(null, 0, value, 0);
+        return new AutofillValue(AUTOFILL_TYPE_TOGGLE, value);
     }
 
     /**
@@ -197,7 +280,7 @@
      * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.
      */
     public static AutofillValue forList(int value) {
-        return new AutofillValue(null, value, false, 0);
+        return new AutofillValue(AUTOFILL_TYPE_LIST, value);
     }
 
     /**
@@ -206,6 +289,6 @@
      * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.
      */
     public static AutofillValue forDate(long value) {
-        return new AutofillValue(null, 0, false, value);
+        return new AutofillValue(AUTOFILL_TYPE_DATE, value);
     }
 }
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 053574f..020e80a 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -23,6 +23,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
@@ -38,6 +39,8 @@
  * @attr ref android.R.styleable#AbsSpinner_entries
  */
 public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
+    private static final String LOG_TAG = AbsSpinner.class.getSimpleName();
+
     SpinnerAdapter mAdapter;
 
     int mHeightMeasureSpec;
@@ -514,8 +517,11 @@
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        final int position = value.getListValue();
-        setSelection(position);
+        if (value.isList()) {
+            setSelection(value.getListValue());
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index d246405..899a824 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -28,6 +28,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.SoundEffectConstants;
 import android.view.ViewDebug;
@@ -55,6 +56,7 @@
  * </p>
  */
 public abstract class CompoundButton extends Button implements Checkable {
+    private static final String LOG_TAG = CompoundButton.class.getSimpleName();
 
     private boolean mChecked;
     private boolean mBroadcasting;
@@ -578,14 +580,18 @@
     public void onProvideAutofillStructure(ViewStructure structure, int flags) {
         super.onProvideAutofillStructure(structure, flags);
 
-        structure.setSanitized(mCheckedFromResource);
+        structure.setDataIsSensitive(!mCheckedFromResource);
     }
 
     @Override
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        setChecked(value.getToggleValue());
+        if (value.isToggle()) {
+            setChecked(value.getToggleValue());
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 31a88d4..f63573f 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -29,6 +29,7 @@
 import android.os.Parcelable;
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewStructure;
@@ -83,6 +84,8 @@
  */
 @Widget
 public class DatePicker extends FrameLayout {
+    private static final String LOG_TAG = DatePicker.class.getSimpleName();
+
     /**
      * Presentation mode for the Holo-style date picker that uses a set of
      * {@link android.widget.NumberPicker}s.
@@ -775,7 +778,11 @@
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        mDelegate.updateDate(value.getDateValue());
+        if (value.isDate()) {
+            mDelegate.updateDate(value.getDateValue());
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index dc9976d..5e8279a 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -55,6 +55,7 @@
  *
  */
 public class RadioGroup extends LinearLayout {
+    private static final String LOG_TAG = RadioGroup.class.getSimpleName();
 
     // holds the checked id; the selection is empty by default
     private int mCheckedId = -1;
@@ -421,14 +422,21 @@
     @Override
     public void onProvideAutofillStructure(ViewStructure structure, int flags) {
         super.onProvideAutofillStructure(structure, flags);
-        structure.setSanitized(mCheckedId == mInitialCheckedId);
+        structure.setDataIsSensitive(mCheckedId != mInitialCheckedId);
     }
 
     @Override
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        final int index = value.getListValue();
+        int index;
+        if (value.isList()) {
+            index = value.getListValue();
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+            return;
+        }
+
         final View child = getChildAt(index);
         if (child == null) {
             Log.w(VIEW_LOG_TAG, "RadioGroup.autoFill(): no child with index " + index);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d591316f..d04f70f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9900,7 +9900,7 @@
         final boolean isPassword = hasPasswordTransformationMethod()
                 || isPasswordInputType(getInputType());
         if (forAutofill) {
-            structure.setSanitized(mTextFromResource);
+            structure.setDataIsSensitive(!mTextFromResource);
         }
 
         if (!isPassword || forAutofill) {
@@ -10014,10 +10014,12 @@
 
     @Override
     public void autofill(AutofillValue value) {
-        final CharSequence text = value.getTextValue();
-
-        if (text != null && isTextEditable()) {
-            setText(text, mBufferType, true, 0);
+        if (value.isText()) {
+            if (isTextEditable()) {
+                setText(value.getTextValue(), mBufferType, true, 0);
+            }
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
         }
     }
 
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 9825f1e..cfa78b5 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -27,6 +27,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewStructure;
@@ -53,6 +54,8 @@
  */
 @Widget
 public class TimePicker extends FrameLayout {
+    private static final String LOG_TAG = TimePicker.class.getSimpleName();
+
     /**
      * Presentation mode for the Holo-style time picker that uses a set of
      * {@link android.widget.NumberPicker}s.
@@ -530,7 +533,11 @@
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        mDelegate.setDate(value.getDateValue());
+        if (value.isDate()) {
+            mDelegate.setDate(value.getDateValue());
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a7e900a..76d8af1 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -132,9 +132,6 @@
         Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
         preloadOpenGL();
         Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
-        preloadOpenGL();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
         preloadSharedLibraries();
         preloadTextResources();
         // Ask the WebViewFactory to do any initialization that must run in the zygote process,
@@ -585,7 +582,6 @@
             OsConstants.CAP_SYS_MODULE,
             OsConstants.CAP_SYS_NICE,
             OsConstants.CAP_SYS_PTRACE,
-            OsConstants.CAP_SYS_RESOURCE,
             OsConstants.CAP_SYS_TIME,
             OsConstants.CAP_SYS_TTY_CONFIG,
             OsConstants.CAP_WAKE_ALARM
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index c1bb69d..e64a574 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -417,11 +417,7 @@
     // For wide gamut images, we will leave the color space on the SkBitmap.  Otherwise,
     // use the default.
     SkImageInfo bitmapInfo = decodeInfo;
-    sk_sp<SkColorSpace> srgb =
-            SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                  SkColorSpace::kSRGB_Gamut,
-                                  SkColorSpace::kNonLinearBlending_ColorSpaceFlag);
-    if (decodeInfo.colorSpace() == srgb.get()) {
+    if (decodeInfo.colorSpace() && decodeInfo.colorSpace()->isSRGB()) {
         bitmapInfo = bitmapInfo.makeColorSpace(GraphicsJNI::colorSpaceForType(decodeColorType));
     }
 
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 7c56c7b..e66587a 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -461,12 +461,7 @@
 }
 
 bool GraphicsJNI::isColorSpaceSRGB(SkColorSpace* colorSpace) {
-    return colorSpace == nullptr
-            || colorSpace == SkColorSpace::MakeSRGB().get()
-            || colorSpace == SkColorSpace::MakeRGB(
-                  SkColorSpace::kSRGB_RenderTargetGamma,
-                  SkColorSpace::kSRGB_Gamut,
-                  SkColorSpace::kNonLinearBlending_ColorSpaceFlag).get();
+    return colorSpace == nullptr || colorSpace->isSRGB();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 248fd15..c547ae5 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2307,37 +2307,9 @@
         </attr>
 
         <!-- Describes the content of a view so that a autofill service can fill in the appropriate
-             data. Multiple flags can be combined to mean e.g. emailAddress or postalAddress. -->
-        <attr name="autofillHint">
-            <!-- No hint. -->
-            <flag name="none" value="0" />
-            <!-- The view contains an email address. -->
-            <flag name="emailAddress" value="0x1" />
-            <!-- The view contains a real name. -->
-            <flag name="name" value="0x2" />
-            <!-- The view contains a user name. -->
-            <flag name="username" value="0x4" />
-            <!-- The view contains a password. -->
-            <flag name="password" value="0x8" />
-            <!-- The view contains a phone number. -->
-            <flag name="phone" value="0x10" />
-            <!-- The view contains a postal address. -->
-            <flag name="postalAddress" value="0x20" />
-            <!-- The view contains a postal code. -->
-            <flag name="postalCode" value="0x40" />
-            <!-- The view contains a credit card number. -->
-            <flag name="creditCardNumber" value="0x80" />
-            <!-- The view contains a credit card security code -->
-            <flag name="creditCardSecurityCode" value="0x100" />
-            <!-- The view contains a credit card expiration date -->
-            <flag name="creditCardExpirationDate" value="0x200" />
-            <!-- The view contains the month a credit card expires -->
-            <flag name="creditCardExpirationMonth" value="0x400" />
-            <!-- The view contains the year a credit card expires -->
-            <flag name="creditCardExpirationYear" value="0x800" />
-            <!-- The view contains the day a credit card expires -->
-            <flag name="creditCardExpirationDay" value="0x1000" />
-        </attr>
+             data. Multiple hints can be combined in a comma separated list or an array of strings
+             to mean e.g. emailAddress or postalAddress. -->
+        <attr name="autofillHint" format="string|reference" />
 
         <!-- Hints the Android System whether the view node associated with this View should be
              included in a view structure used for autofill purposes. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index bfe666e..67050f7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2401,6 +2401,9 @@
         <!-- Load order of overlay package. -->
         <attr name="priority" />
 
+        <!-- Whether the given RRO is static or not. -->
+        <attr name="isStatic" format="boolean" />
+
     </declare-styleable>
 
     <!-- Declaration of an {@link android.content.Intent} object in XML.  May
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index 483d05b..ba14843 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -75,7 +75,7 @@
         <item>ce-RU</item> <!-- Chechen (Russia) -->
         <item>cgg-UG</item> <!-- Chiga (Uganda) -->
         <item>chr-US</item> <!-- Cherokee (United States) -->
-        <item>cs-CZ</item> <!-- Czech (Czech Republic) -->
+        <item>cs-CZ</item> <!-- Czech (Czechia) -->
         <item>cy-GB</item> <!-- Welsh (United Kingdom) -->
         <item>da-DK</item> <!-- Danish (Denmark) -->
         <item>da-GL</item> <!-- Danish (Greenland) -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index bb3f1c3..59432cd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2806,6 +2806,7 @@
         <public name="fontProviderPackage" />
         <public name="importantForAutofill" />
         <public name="recycleEnabled"/>
+        <public name="isStatic" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 29c6b79..1ae922a 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -79,7 +79,7 @@
     <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
 
-    <!-- Czech Republic: 7-8 digits, starting with 9, plus EU:
+    <!-- Czechia: 7-8 digits, starting with 9, plus EU:
          http://www.o2.cz/osobni/en/services-by-alphabet/91670-premium_sms.html -->
     <shortcode country="cz" premium="9\\d{6,7}" free="116\\d{3}" />
 
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 76598a0..38a1a46 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -248,6 +248,9 @@
         <font weight="400" style="normal">NotoSansCham-Regular.ttf</font>
         <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
     </family>
+    <family lang="und-Avst">
+        <font weight="400" style="normal">NotoSansAvestan-Regular.ttf</font>
+    </family>
     <family lang="und-Bali">
         <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font>
     </family>
@@ -257,6 +260,9 @@
     <family lang="und-Batk">
         <font weight="400" style="normal">NotoSansBatak-Regular.ttf</font>
     </family>
+    <family lang="und-Brah">
+        <font weight="400" style="normal">NotoSansBrahmi-Regular.ttf</font>
+    </family>
     <family lang="und-Bugi">
         <font weight="400" style="normal">NotoSansBuginese-Regular.ttf</font>
     </family>
@@ -266,33 +272,75 @@
     <family lang="und-Cans">
         <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font>
     </family>
+    <family lang="und-Cari">
+        <font weight="400" style="normal">NotoSansCarian-Regular.ttf</font>
+    </family>
     <family lang="und-Cher">
         <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
     </family>
     <family lang="und-Copt">
         <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font>
     </family>
+    <family lang="und-Xsux">
+        <font weight="400" style="normal">NotoSansCuneiform-Regular.ttf</font>
+    </family>
+    <family lang="und-Cprt">
+        <font weight="400" style="normal">NotoSansCypriot-Regular.ttf</font>
+    </family>
+    <family lang="und-Dsrt">
+        <font weight="400" style="normal">NotoSansDeseret-Regular.ttf</font>
+    </family>
+    <family lang="und-Egyp">
+        <font weight="400" style="normal">NotoSansEgyptianHieroglyphs-Regular.ttf</font>
+    </family>
     <family lang="und-Glag">
         <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font>
     </family>
+    <family lang="und-Goth">
+        <font weight="400" style="normal">NotoSansGothic-Regular.ttf</font>
+    </family>
     <family lang="und-Hano">
         <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font>
     </family>
+    <family lang="und-Armi">
+        <font weight="400" style="normal">NotoSansImperialAramaic-Regular.ttf</font>
+    </family>
+    <family lang="und-Phli">
+        <font weight="400" style="normal">NotoSansInscriptionalPahlavi-Regular.ttf</font>
+    </family>
+    <family lang="und-Prti">
+        <font weight="400" style="normal">NotoSansInscriptionalParthian-Regular.ttf</font>
+    </family>
     <family lang="und-Java">
         <font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font>
     </family>
+    <family lang="und-Kthi">
+        <font weight="400" style="normal">NotoSansKaithi-Regular.ttf</font>
+    </family>
     <family lang="und-Kali">
         <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font>
     </family>
+    <family lang="und-Khar">
+        <font weight="400" style="normal">NotoSansKharoshthi-Regular.ttf</font>
+    </family>
     <family lang="und-Lepc">
         <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font>
     </family>
     <family lang="und-Limb">
         <font weight="400" style="normal">NotoSansLimbu-Regular.ttf</font>
     </family>
+    <family lang="und-Linb">
+        <font weight="400" style="normal">NotoSansLinearB-Regular.ttf</font>
+    </family>
     <family lang="und-Lisu">
         <font weight="400" style="normal">NotoSansLisu-Regular.ttf</font>
     </family>
+    <family lang="und-Lyci">
+        <font weight="400" style="normal">NotoSansLycian-Regular.ttf</font>
+    </family>
+    <family lang="und-Lydi">
+        <font weight="400" style="normal">NotoSansLydian-Regular.ttf</font>
+    </family>
     <family lang="und-Mand">
         <font weight="400" style="normal">NotoSansMandaic-Regular.ttf</font>
     </family>
@@ -305,12 +353,33 @@
     <family lang="und-Nkoo">
         <font weight="400" style="normal">NotoSansNKo-Regular.ttf</font>
     </family>
+    <family lang="und-Ogam">
+        <font weight="400" style="normal">NotoSansOgham-Regular.ttf</font>
+    </family>
     <family lang="und-Olck">
         <font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font>
     </family>
+    <family lang="und-Ital">
+        <font weight="400" style="normal">NotoSansOldItalic-Regular.ttf</font>
+    </family>
+    <family lang="und-Xpeo">
+        <font weight="400" style="normal">NotoSansOldPersian-Regular.ttf</font>
+    </family>
+    <family lang="und-Sarb">
+        <font weight="400" style="normal">NotoSansOldSouthArabian-Regular.ttf</font>
+    </family>
+    <family lang="und-Orkh">
+        <font weight="400" style="normal">NotoSansOldTurkic-Regular.ttf</font>
+    </family>
+    <family lang="und-Osma">
+        <font weight="400" style="normal">NotoSansOsmanya-Regular.ttf</font>
+    </family>
     <family lang="und-Phag">
         <font weight="400" style="normal">NotoSansPhagsPa-Regular.ttf</font>
     </family>
+    <family lang="und-Phnx">
+        <font weight="400" style="normal">NotoSansPhoenician-Regular.ttf</font>
+    </family>
     <family lang="und-Rjng">
         <font weight="400" style="normal">NotoSansRejang-Regular.ttf</font>
     </family>
@@ -323,6 +392,9 @@
     <family lang="und-Saur">
         <font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font>
     </family>
+    <family lang="und-Shaw">
+        <font weight="400" style="normal">NotoSansShavian-Regular.ttf</font>
+    </family>
     <family lang="und-Sund">
         <font weight="400" style="normal">NotoSansSundanese-Regular.ttf</font>
     </family>
@@ -358,6 +430,9 @@
     <family lang="und-Tfng">
         <font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font>
     </family>
+    <family lang="und-Ugar">
+        <font weight="400" style="normal">NotoSansUgaritic-Regular.ttf</font>
+    </family>
     <family lang="und-Vaii">
         <font weight="400" style="normal">NotoSansVai-Regular.ttf</font>
     </family>
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 2a2e14b..7289429 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -157,12 +157,10 @@
 
     /**
      * Specify a bitmap for the canvas to draw into. All canvas state such as
-     * layers, filters, and the save/restore stack are reset. Additionally,
+     * layers, filters, and the save/restore stack are reset with the exception
+     * of the current matrix and clip stack. Additionally, as a side-effect
      * the canvas' target density is updated to match that of the bitmap.
      *
-     * Prior to API level {@value Build.VERSION_CODES#O} the current matrix and
-     * clip stack were preserved.
-     *
      * @param bitmap Specifies a mutable bitmap for the canvas to draw into.
      * @see #setDensity(int)
      * @see #getDensity()
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 908ec50..929ac22 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -490,16 +490,16 @@
          *     <tr>
          *         <td>Opto-electronic transfer function (OETF)</td>
          *         <td colspan="4">\(\begin{equation}
-         *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\
-         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases}
+         *             C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\
+         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases}
          *             \end{equation}\)
          *         </td>
          *     </tr>
          *     <tr>
          *         <td>Electro-optical transfer function (EOTF)</td>
          *         <td colspan="4">\(\begin{equation}
-         *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\
-         *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases}
+         *             C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.039 \\
+         *             \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.039 \end{cases}
          *             \end{equation}\)
          *         </td>
          *     </tr>
@@ -1482,7 +1482,7 @@
                 "Display P3",
                 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
                 ILLUMINANT_D65,
-                new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4),
+                new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.039, 2.4),
                 Named.DISPLAY_P3.ordinal()
         );
         sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 363aa83..812e4d8 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -73,9 +73,39 @@
 // Canvas state operations: Replace Bitmap
 // ----------------------------------------------------------------------------
 
+class ClipCopier : public SkCanvas::ClipVisitor {
+public:
+    explicit ClipCopier(SkCanvas* dstCanvas) : m_dstCanvas(dstCanvas) {}
+
+    virtual void clipRect(const SkRect& rect, SkClipOp op, bool antialias) {
+        m_dstCanvas->clipRect(rect, op, antialias);
+    }
+    virtual void clipRRect(const SkRRect& rrect, SkClipOp op, bool antialias) {
+        m_dstCanvas->clipRRect(rrect, op, antialias);
+    }
+    virtual void clipPath(const SkPath& path, SkClipOp op, bool antialias) {
+        m_dstCanvas->clipPath(path, op, antialias);
+    }
+
+private:
+    SkCanvas* m_dstCanvas;
+};
+
 void SkiaCanvas::setBitmap(const SkBitmap& bitmap) {
-    mCanvasOwned.reset(new SkCanvas(bitmap));
-    mCanvas = mCanvasOwned.get();
+    SkCanvas* newCanvas = new SkCanvas(bitmap);
+
+    if (!bitmap.isNull()) {
+        // Copy the canvas matrix & clip state.
+        newCanvas->setMatrix(mCanvas->getTotalMatrix());
+
+        ClipCopier copier(newCanvas);
+        mCanvas->replayClips(&copier);
+    }
+
+    // deletes the previously owned canvas (if any)
+    mCanvasOwned = std::unique_ptr<SkCanvas>(newCanvas);
+    mCanvas = newCanvas;
+
     // clean up the old save stack
     mSaveStack.reset(nullptr);
 }
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 6e15d8d..62fd395 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -2679,6 +2679,9 @@
         /**
          * The ID of the TV channel that provides this TV program.
          *
+         * <p>This value cannot be changed once it's set. Trying to modify it will make the update
+         * fail.
+         *
          * <p>This is a part of the channel URI and matches to {@link BaseColumns#_ID}.
          *
          * <p>This is a required field.
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
index fa5ba73..ee7885d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
@@ -132,7 +132,7 @@
                 mCategoryByKeyMap.put(category.key, category);
             }
             backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
-            normalizePriority(context, mCategoryByKeyMap);
+            sortCategories(context, mCategoryByKeyMap);
             filterDuplicateTiles(mCategoryByKeyMap);
         }
     }
@@ -188,17 +188,17 @@
     }
 
     /**
-     * Normalize priority values on tiles across injected from all apps to make sure they don't set
-     * the same priority value. However internal tiles' priority remains unchanged.
+     * Sort the tiles injected from all apps such that if they have the same priority value,
+     * they wil lbe sorted by package name.
      * <p/>
-     * A list of tiles are considered normalized when their priority value increases in a linear
+     * A list of tiles are considered sorted when their priority value decreases in a linear
      * scan.
      */
     @VisibleForTesting
-    synchronized void normalizePriority(Context context,
+    synchronized void sortCategories(Context context,
             Map<String, DashboardCategory> categoryByKeyMap) {
         for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
-            normalizePriorityForExternalTiles(context, categoryEntry.getValue());
+            sortCategoriesForExternalTiles(context, categoryEntry.getValue());
         }
     }
 
@@ -228,39 +228,34 @@
     }
 
     /**
-     * Normalize priority value for tiles within a single {@code DashboardCategory}.
+     * Sort priority value for tiles within a single {@code DashboardCategory}.
      *
-     * @see #normalizePriority(Context, Map)
+     * @see #sortCategories(Context, Map)
      */
-    private synchronized void normalizePriorityForExternalTiles(Context context,
+    private synchronized void sortCategoriesForExternalTiles(Context context,
             DashboardCategory dashboardCategory) {
         final String skipPackageName = context.getPackageName();
 
-        // Sort tiles based on [package, priority within package]
+        // Sort tiles based on [priority, package within priority]
         Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> {
             final String package1 = tile1.intent.getComponent().getPackageName();
             final String package2 = tile2.intent.getComponent().getPackageName();
             final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
-            // First sort by package name
+            // First sort by priority
+            final int priorityCompare = tile2.priority - tile1.priority;
+            if (priorityCompare != 0) {
+                return priorityCompare;
+            }
+            // Then sort by package name, skip package take precedence
             if (packageCompare != 0) {
-                return packageCompare;
-            } else if (TextUtils.equals(package1, skipPackageName)) {
-                return 0;
+                if (TextUtils.equals(package1, skipPackageName)) {
+                    return -1;
+                }
+                if (TextUtils.equals(package2, skipPackageName)) {
+                    return 1;
+                }
             }
-            // Then sort by priority
-            return tile1.priority - tile2.priority;
+            return packageCompare;
         });
-        // Update priority for all items so no package define the same priority value.
-        final int count = dashboardCategory.tiles.size();
-        for (int i = 0; i < count; i++) {
-            final String packageName =
-                    dashboardCategory.tiles.get(i).intent.getComponent().getPackageName();
-            if (TextUtils.equals(packageName, skipPackageName)) {
-                // We skip this tile because it's a intent pointing to our own app. We trust the
-                // priority is set correctly, so don't normalize.
-                continue;
-            }
-            dashboardCategory.tiles.get(i).priority = i;
-        }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 6fe581e..48f3e2a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -47,6 +47,7 @@
 import android.text.style.TtsSpan;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
 
 import java.util.ArrayList;
@@ -112,9 +113,14 @@
     private static final int PSK_WPA2 = 2;
     private static final int PSK_WPA_WPA2 = 3;
 
-    public static final int SIGNAL_LEVELS = 4;
+    /**
+     * The number of distinct wifi levels.
+     *
+     * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}.
+     */
+    public static final int SIGNAL_LEVELS = 5;
 
-    static final int UNREACHABLE_RSSI = Integer.MAX_VALUE;
+    public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
 
     private final Context mContext;
 
@@ -170,8 +176,8 @@
             }
         }
         update(mConfig, mInfo, mNetworkInfo);
-        mRssi = getRssi();
-        mSeen = getSeen();
+        updateRssi();
+        updateSeen();
         mId = sLastId.incrementAndGet();
     }
 
@@ -369,27 +375,57 @@
         return mInfo;
     }
 
+    /**
+     * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1.
+     *
+     * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will
+     * always return at least 0.
+     */
     public int getLevel() {
-        if (!isReachable()) {
-            return -1;
-        }
         return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
     }
 
     public int getRssi() {
+        return mRssi;
+    }
+
+    /**
+     * Updates {@link #mRssi}.
+     *
+     * <p>If the given connection is active, the existing value of {@link #mRssi} will be returned.
+     * If the given AccessPoint is not active, a value will be calculated from previous scan
+     * results, returning the best RSSI for all matching AccessPoints. If the access point is not
+     * connected and there are no scan results, the rssi will be set to {@link #UNREACHABLE_RSSI}.
+     *
+     * <p>Old scan results will be evicted from the cache when this method is invoked.
+     */
+    private void updateRssi() {
         evictOldScanResults();
-        int rssi = Integer.MIN_VALUE;
+
+        if (this.isActive()) {
+            return;
+        }
+
+        int rssi = UNREACHABLE_RSSI;
         for (ScanResult result : mScanResultCache.values()) {
             if (result.level > rssi) {
                 rssi = result.level;
             }
         }
 
-        return rssi;
+        mRssi = rssi;
     }
 
-    public long getSeen() {
+    /**
+     * Updates {@link #mSeen} based on the scan result cache.
+     *
+     * <p>Old scan results will be evicted from the cache when this method is invoked.
+     */
+    private void updateSeen() {
         evictOldScanResults();
+
+        // TODO(sghuman): Set to now if connected
+
         long seen = 0;
         for (ScanResult result : mScanResultCache.values()) {
             if (result.timestamp > seen) {
@@ -397,7 +433,7 @@
             }
         }
 
-        return seen;
+        mSeen = seen;
     }
 
     public NetworkInfo getNetworkInfo() {
@@ -822,13 +858,12 @@
 
     boolean update(ScanResult result) {
         if (matches(result)) {
+            int oldLevel = getLevel();
+
             /* Add or update the scan result for the BSSID */
             mScanResultCache.put(result.BSSID, result);
-
-            int oldLevel = getLevel();
-            int oldRssi = getRssi();
-            mSeen = getSeen();
-            mRssi = (getRssi() + oldRssi)/2;
+            updateSeen();
+            updateRssi();
             int newLevel = getLevel();
 
             if (newLevel > 0 && newLevel != oldLevel && mAccessPointListener != null) {
@@ -848,7 +883,7 @@
         return false;
     }
 
-    boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
+    public boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
         boolean reorder = false;
         if (info != null && isInfoForThisAccessPoint(config, info)) {
             reorder = (mInfo == null);
@@ -877,10 +912,16 @@
         }
     }
 
+    @VisibleForTesting
     void setRssi(int rssi) {
         mRssi = rssi;
     }
 
+    /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */
+    void setUnreachable() {
+        setRssi(AccessPoint.UNREACHABLE_RSSI);
+    }
+
     int getRankingScore() {
         return mRankingScore;
     }
@@ -890,7 +931,7 @@
     }
 
     /** Return true if the current RSSI is reachable, and false otherwise. */
-    boolean isReachable() {
+    public boolean isReachable() {
         return mRssi != UNREACHABLE_RSSI;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 50972c7..c9fa017 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -223,8 +223,7 @@
         }
 
         final Context context = getContext();
-        int level = WifiManager.calculateSignalLevel(
-                mAccessPoint.getRssi(), WifiManager.RSSI_LEVELS);
+        int level = mAccessPoint.getLevel();
         int wifiBadge = mAccessPoint.getBadge();
         if (level != mLevel || wifiBadge != mWifiBadge) {
             mLevel = level;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 8421c2c..55c886e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -471,7 +471,8 @@
                     accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
                 }
                 if (mIncludeSaved) {
-                    // If saved network not present in scan result then set its Rssi to MAX_VALUE
+                    // If saved network not present in scan result then set its Rssi to
+                    // UNREACHABLE_RSSI
                     boolean apFound = false;
                     for (ScanResult result : results) {
                         if (result.SSID.equals(accessPoint.getSsidStr())) {
@@ -480,7 +481,7 @@
                         }
                     }
                     if (!apFound) {
-                        accessPoint.setRssi(Integer.MAX_VALUE);
+                        accessPoint.setUnreachable();
                     }
                     accessPoints.add(accessPoint);
                     apMap.put(accessPoint.getSsidStr(), accessPoint);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index ec0190c..e8a58c1 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -89,21 +89,10 @@
 
     @Test
     public void testThatCopyAccessPoint_scanCacheShouldMatch() {
-        Bundle bundle = new Bundle();
-        ArrayList<ScanResult> scanResults = new ArrayList<>();
-        for (int i = 0; i < 5; i++) {
-            ScanResult scanResult = new ScanResult();
-            scanResult.level = i;
-            scanResult.BSSID = "bssid-" + i;
-            scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
-            scanResults.add(scanResult);
-        }
-
-        bundle.putParcelableArrayList("key_scanresultcache", scanResults);
-        AccessPoint original = new AccessPoint(mContext, bundle);
+        AccessPoint original = createAccessPointWithScanResultCache();
         assertThat(original.getRssi()).isEqualTo(4);
         AccessPoint copy = new AccessPoint(mContext, createWifiConfiguration());
-        assertThat(copy.getRssi()).isEqualTo(Integer.MIN_VALUE);
+        assertThat(copy.getRssi()).isEqualTo(AccessPoint.UNREACHABLE_RSSI);
         copy.copyFrom(original);
         assertThat(original.getRssi()).isEqualTo(copy.getRssi());
     }
@@ -190,6 +179,37 @@
         assertThat(points.indexOf(firstName)).isLessThan(points.indexOf(lastname));
     }
 
+    @Test
+    public void testRssiIsSetFromScanResults() {
+        AccessPoint ap = createAccessPointWithScanResultCache();
+        int originalRssi = ap.getRssi();
+        assertThat(originalRssi).isNotEqualTo(AccessPoint.UNREACHABLE_RSSI);
+    }
+
+    @Test
+    public void testGetRssiShouldReturnSetRssiValue() {
+        AccessPoint ap = createAccessPointWithScanResultCache();
+        int originalRssi = ap.getRssi();
+        int newRssi = originalRssi - 10;
+        ap.setRssi(newRssi);
+        assertThat(ap.getRssi()).isEqualTo(newRssi);
+    }
+
+    private AccessPoint createAccessPointWithScanResultCache() {
+        Bundle bundle = new Bundle();
+        ArrayList<ScanResult> scanResults = new ArrayList<>();
+        for (int i = 0; i < 5; i++) {
+            ScanResult scanResult = new ScanResult();
+            scanResult.level = i;
+            scanResult.BSSID = "bssid-" + i;
+            scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
+            scanResults.add(scanResult);
+        }
+
+        bundle.putParcelableArrayList("key_scanresultcache", scanResults);
+        return new AccessPoint(mContext, bundle);
+    }
+
     private WifiConfiguration createWifiConfiguration() {
         WifiConfiguration configuration = new WifiConfiguration();
         configuration.BSSID = "bssid";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java
index 434241d..8d61338 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java
@@ -120,7 +120,7 @@
     }
 
     @Test
-    public void normalizePriority_singlePackage_shouldReorderBasedOnPriority() {
+    public void sortCategories_singlePackage_shouldReorderBasedOnPriority() {
         // Create some fake tiles that are not sorted.
         final String testPackage = "com.android.test";
         final DashboardCategory category = new DashboardCategory();
@@ -141,22 +141,18 @@
         category.tiles.add(tile3);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
-        // Normalize their priorities
-        mCategoryManager.normalizePriority(ShadowApplication.getInstance().getApplicationContext(),
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
                 mCategoryByKeyMap);
 
         // Verify they are now sorted.
-        assertThat(category.tiles.get(0)).isSameAs(tile2);
+        assertThat(category.tiles.get(0)).isSameAs(tile3);
         assertThat(category.tiles.get(1)).isSameAs(tile1);
-        assertThat(category.tiles.get(2)).isSameAs(tile3);
-        // Verify their priority is normalized
-        assertThat(category.tiles.get(0).priority).isEqualTo(0);
-        assertThat(category.tiles.get(1).priority).isEqualTo(1);
-        assertThat(category.tiles.get(2).priority).isEqualTo(2);
+        assertThat(category.tiles.get(2)).isSameAs(tile2);
     }
 
     @Test
-    public void normalizePriority_multiPackage_shouldReorderBasedOnPackageAndPriority() {
+    public void sortCategories_multiPackage_shouldReorderBasedOnPackageAndPriority() {
         // Create some fake tiles that are not sorted.
         final String testPackage1 = "com.android.test1";
         final String testPackage2 = "com.android.test2";
@@ -178,22 +174,18 @@
         category.tiles.add(tile3);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
-        // Normalize their priorities
-        mCategoryManager.normalizePriority(ShadowApplication.getInstance().getApplicationContext(),
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
                 mCategoryByKeyMap);
 
         // Verify they are now sorted.
-        assertThat(category.tiles.get(0)).isSameAs(tile3);
-        assertThat(category.tiles.get(1)).isSameAs(tile2);
-        assertThat(category.tiles.get(2)).isSameAs(tile1);
-        // Verify their priority is normalized
-        assertThat(category.tiles.get(0).priority).isEqualTo(0);
-        assertThat(category.tiles.get(1).priority).isEqualTo(1);
-        assertThat(category.tiles.get(2).priority).isEqualTo(2);
+        assertThat(category.tiles.get(0)).isSameAs(tile2);
+        assertThat(category.tiles.get(1)).isSameAs(tile1);
+        assertThat(category.tiles.get(2)).isSameAs(tile3);
     }
 
     @Test
-    public void normalizePriority_internalPackageTiles_shouldSkipTileForInternalPackage() {
+    public void sortCategories_internalPackageTiles_shouldSkipTileForInternalPackage() {
         // Create some fake tiles that are not sorted.
         final String testPackage =
                 ShadowApplication.getInstance().getApplicationContext().getPackageName();
@@ -215,18 +207,82 @@
         category.tiles.add(tile3);
         mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
 
-        // Normalize their priorities
-        mCategoryManager.normalizePriority(ShadowApplication.getInstance().getApplicationContext(),
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
                 mCategoryByKeyMap);
 
         // Verify the sorting order is not changed
         assertThat(category.tiles.get(0)).isSameAs(tile1);
         assertThat(category.tiles.get(1)).isSameAs(tile2);
         assertThat(category.tiles.get(2)).isSameAs(tile3);
-        // Verify their priorities are not changed.
-        assertThat(category.tiles.get(0).priority).isEqualTo(100);
-        assertThat(category.tiles.get(1).priority).isEqualTo(100);
-        assertThat(category.tiles.get(2).priority).isEqualTo(50);
+    }
+
+    @Test
+    public void sortCategories_internalAndExternalPackageTiles_shouldRetainPriorityOrdering() {
+        // Inject one external tile among internal tiles.
+        final String testPackage =
+            ShadowApplication.getInstance().getApplicationContext().getPackageName();
+        final String testPackage2 = "com.google.test2";
+        final DashboardCategory category = new DashboardCategory();
+        final Tile tile1 = new Tile();
+        tile1.intent = new Intent().setComponent(new ComponentName(testPackage, "class1"));
+        tile1.priority = 2;
+        final Tile tile2 = new Tile();
+        tile2.intent = new Intent().setComponent(new ComponentName(testPackage, "class2"));
+        tile2.priority = 1;
+        final Tile tile3 = new Tile();
+        tile3.intent = new Intent().setComponent(new ComponentName(testPackage2, "class0"));
+        tile3.priority = 0;
+        final Tile tile4 = new Tile();
+        tile4.intent = new Intent().setComponent(new ComponentName(testPackage, "class3"));
+        tile4.priority = -1;
+        category.tiles.add(tile1);
+        category.tiles.add(tile2);
+        category.tiles.add(tile3);
+        category.tiles.add(tile4);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
+
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
+            mCategoryByKeyMap);
+
+        // Verify the sorting order is not changed
+        assertThat(category.tiles.get(0)).isSameAs(tile1);
+        assertThat(category.tiles.get(1)).isSameAs(tile2);
+        assertThat(category.tiles.get(2)).isSameAs(tile3);
+        assertThat(category.tiles.get(3)).isSameAs(tile4);
+    }
+
+    @Test
+    public void sortCategories_samePriority_internalPackageTileShouldTakePrecedence() {
+        // Inject one external tile among internal tiles with same priority.
+        final String testPackage =
+            ShadowApplication.getInstance().getApplicationContext().getPackageName();
+        final String testPackage2 = "com.google.test2";
+        final String testPackage3 = "com.abcde.test3";
+        final DashboardCategory category = new DashboardCategory();
+        final Tile tile1 = new Tile();
+        tile1.intent = new Intent().setComponent(new ComponentName(testPackage2, "class1"));
+        tile1.priority = 1;
+        final Tile tile2 = new Tile();
+        tile2.intent = new Intent().setComponent(new ComponentName(testPackage, "class2"));
+        tile2.priority = 1;
+        final Tile tile3 = new Tile();
+        tile3.intent = new Intent().setComponent(new ComponentName(testPackage3, "class3"));
+        tile3.priority = 1;
+        category.tiles.add(tile1);
+        category.tiles.add(tile2);
+        category.tiles.add(tile3);
+        mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
+
+        // Sort their priorities
+        mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(),
+            mCategoryByKeyMap);
+
+        // Verify the sorting order is internal first, follow by package name ordering
+        assertThat(category.tiles.get(0)).isSameAs(tile2);
+        assertThat(category.tiles.get(1)).isSameAs(tile3);
+        assertThat(category.tiles.get(2)).isSameAs(tile1);
     }
 
     @Test
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 428b7b8..4b932a3 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -180,6 +180,9 @@
     <!-- accessibility -->
     <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
 
+    <!-- to control accessibility volume -->
+    <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
+
     <application
         android:name=".SystemUIApplication"
         android:persistent="true"
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index bb7e19d..4dfaf45 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -30,6 +30,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManagerImpl;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -73,6 +74,7 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerServiceImpl;
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.leak.LeakReporter;
@@ -200,7 +202,7 @@
                 new DeviceProvisionedControllerImpl(mContext));
 
         mProviders.put(PluginManager.class, () ->
-                new PluginManager(mContext));
+                new PluginManagerImpl(mContext));
 
         mProviders.put(AssistManager.class, () ->
                 new AssistManager(getDependency(DeviceProvisionedController.class), mContext));
@@ -223,7 +225,7 @@
                 getDependency(LeakReporter.class)));
 
         mProviders.put(TunerService.class, () ->
-                new TunerService(mContext));
+                new TunerServiceImpl(mContext));
 
         mProviders.put(StatusBarWindowManager.class, () ->
                 new StatusBarWindowManager(mContext));
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
index 79f78c9..07ac52d 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -62,10 +62,10 @@
     final PluginHandler mPluginHandler;
     private final boolean isDebuggable;
     private final PackageManager mPm;
-    private final PluginManager mManager;
+    private final PluginManagerImpl mManager;
 
     PluginInstanceManager(Context context, String action, PluginListener<T> listener,
-            boolean allowMultiple, Looper looper, VersionInfo version, PluginManager manager) {
+            boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) {
         this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
                 manager, Build.IS_DEBUGGABLE);
     }
@@ -73,7 +73,7 @@
     @VisibleForTesting
     PluginInstanceManager(Context context, PackageManager pm, String action,
             PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version,
-            PluginManager manager, boolean debuggable) {
+            PluginManagerImpl manager, boolean debuggable) {
         mMainHandler = new MainHandler(Looper.getMainLooper());
         mPluginHandler = new PluginHandler(looper);
         mManager = manager;
@@ -346,7 +346,7 @@
                                 .setContentText("Check to see if an OTA is available.\n"
                                         + e.getMessage());
                     }
-                    Intent i = new Intent(PluginManager.DISABLE_PLUGIN).setData(
+                    Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData(
                             Uri.parse("package://" + component.flattenToString()));
                     PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
                     nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java
index 9ad862d..298eaf1 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
@@ -14,276 +14,33 @@
 
 package com.android.systemui.plugins;
 
-import android.app.Notification;
-import android.app.Notification.Action;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.systemui.Dependency;
-import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;
-import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
-import dalvik.system.PathClassLoader;
+public interface PluginManager {
 
-import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.Map;
-
-/**
- * @see Plugin
- */
-public class PluginManager extends BroadcastReceiver {
-
-    public static final String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED";
-
-    static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN";
+    String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED";
 
     // must be one of the channels created in NotificationChannels.java
-    static final String NOTIFICATION_CHANNEL_ID = "ALR";
+    String NOTIFICATION_CHANNEL_ID = "ALR";
 
-    private static PluginManager sInstance;
+    <T extends Plugin> T getOneShotPlugin(Class<T> cls);
+    <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls);
 
-    private final HandlerThread mBackgroundThread;
-    private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
-            = new ArrayMap<>();
-    private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
-    private final ArraySet<String> mOneShotPackages = new ArraySet<>();
-    private final Context mContext;
-    private final PluginInstanceManagerFactory mFactory;
-    private final boolean isDebuggable;
-    private final PluginPrefs mPluginPrefs;
-    private ClassLoaderFilter mParentClassLoader;
-    private boolean mListening;
-    private boolean mHasOneShot;
+    <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls);
+    <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
+            boolean allowMultiple);
+    <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            Class<?> cls);
+    <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            Class cls, boolean allowMultiple);
 
-    public PluginManager(Context context) {
-        this(context, new PluginInstanceManagerFactory(),
-                Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler());
-    }
+    void removePluginListener(PluginListener<?> listener);
 
-    @VisibleForTesting
-    PluginManager(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
-            UncaughtExceptionHandler defaultHandler) {
-        mContext = context;
-        mFactory = factory;
-        mBackgroundThread = new HandlerThread("Plugins");
-        mBackgroundThread.start();
-        isDebuggable = debuggable;
-        mPluginPrefs = new PluginPrefs(mContext);
+    <T> boolean dependsOn(Plugin p, Class<T> cls);
 
-        PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
-                defaultHandler);
-        Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
-        if (isDebuggable) {
-            new Handler(mBackgroundThread.getLooper()).post(() -> {
-                // Plugin dependencies that don't have another good home can go here, but
-                // dependencies that have better places to init can happen elsewhere.
-                Dependency.get(PluginDependencyProvider.class)
-                        .allowPluginDependency(ActivityStarter.class);
-            });
-        }
-    }
-
-    public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
-        ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
-        if (info == null) {
-            throw new RuntimeException(cls + " doesn't provide an interface");
-        }
-        if (TextUtils.isEmpty(info.action())) {
-            throw new RuntimeException(cls + " doesn't provide an action");
-        }
-        return getOneShotPlugin(info.action(), cls);
-    }
-
-    public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
-        if (!isDebuggable) {
-            // Never ever ever allow these on production builds, they are only for prototyping.
-            return null;
-        }
-        if (Looper.myLooper() != Looper.getMainLooper()) {
-            throw new RuntimeException("Must be called from UI thread");
-        }
-        PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null,
-                false, mBackgroundThread.getLooper(), cls, this);
-        mPluginPrefs.addAction(action);
-        PluginInfo<T> info = p.getPlugin();
-        if (info != null) {
-            mOneShotPackages.add(info.mPackage);
-            mHasOneShot = true;
-            startListening();
-            return info.mPlugin;
-        }
-        return null;
-    }
-
-    public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {
-        addPluginListener(listener, cls, false);
-    }
-
-    public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
-            boolean allowMultiple) {
-        addPluginListener(getAction(cls), listener, cls, allowMultiple);
-    }
-
-    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
-            Class<?> cls) {
-        addPluginListener(action, listener, cls, false);
-    }
-
-    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
-            Class cls, boolean allowMultiple) {
-        if (!isDebuggable) {
-            // Never ever ever allow these on production builds, they are only for prototyping.
-            return;
-        }
-        mPluginPrefs.addAction(action);
-        PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
-                allowMultiple, mBackgroundThread.getLooper(), cls, this);
-        p.loadAll();
-        mPluginMap.put(listener, p);
-        startListening();
-    }
-
-    public void removePluginListener(PluginListener<?> listener) {
-        if (!isDebuggable) {
-            // Never ever ever allow these on production builds, they are only for prototyping.
-            return;
-        }
-        if (!mPluginMap.containsKey(listener)) return;
-        mPluginMap.remove(listener).destroy();
-        stopListening();
-    }
-
-    private void startListening() {
-        if (mListening) return;
-        mListening = true;
-        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(PLUGIN_CHANGED);
-        filter.addAction(DISABLE_PLUGIN);
-        filter.addDataScheme("package");
-        mContext.registerReceiver(this, filter);
-        filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
-        mContext.registerReceiver(this, filter);
-    }
-
-    private void stopListening() {
-        // Never stop listening if a one-shot is present.
-        if (!mListening || mHasOneShot) return;
-        mListening = false;
-        mContext.unregisterReceiver(this);
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
-            for (PluginInstanceManager manager : mPluginMap.values()) {
-                manager.loadAll();
-            }
-        } else if (DISABLE_PLUGIN.equals(intent.getAction())) {
-            Uri uri = intent.getData();
-            ComponentName component = ComponentName.unflattenFromString(
-                    uri.toString().substring(10));
-            mContext.getPackageManager().setComponentEnabledSetting(component,
-                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                    PackageManager.DONT_KILL_APP);
-            mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(),
-                    SystemMessage.NOTE_PLUGIN);
-        } else {
-            Uri data = intent.getData();
-            String pkg = data.getEncodedSchemeSpecificPart();
-            if (mOneShotPackages.contains(pkg)) {
-                int icon = mContext.getResources().getIdentifier("tuner", "drawable",
-                        mContext.getPackageName());
-                int color = Resources.getSystem().getIdentifier(
-                        "system_notification_accent_color", "color", "android");
-                String label = pkg;
-                try {
-                    PackageManager pm = mContext.getPackageManager();
-                    label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString();
-                } catch (NameNotFoundException e) {
-                }
-                // Localization not required as this will never ever appear in a user build.
-                final Notification.Builder nb =
-                        new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                                .setSmallIcon(icon)
-                                .setWhen(0)
-                                .setShowWhen(false)
-                                .setPriority(Notification.PRIORITY_MAX)
-                                .setVisibility(Notification.VISIBILITY_PUBLIC)
-                                .setColor(mContext.getColor(color))
-                                .setContentTitle("Plugin \"" + label + "\" has updated")
-                                .setContentText("Restart SysUI for changes to take effect.");
-                Intent i = new Intent("com.android.systemui.action.RESTART").setData(
-                            Uri.parse("package://" + pkg));
-                PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
-                nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build());
-                mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg,
-                        SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL);
-            }
-            clearClassLoader(pkg);
-            if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
-                for (PluginInstanceManager manager : mPluginMap.values()) {
-                    manager.onPackageChange(pkg);
-                }
-            } else {
-                for (PluginInstanceManager manager : mPluginMap.values()) {
-                    manager.onPackageRemoved(pkg);
-                }
-            }
-        }
-    }
-
-    public ClassLoader getClassLoader(String sourceDir, String pkg) {
-        if (mClassLoaders.containsKey(pkg)) {
-            return mClassLoaders.get(pkg);
-        }
-        ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader());
-        mClassLoaders.put(pkg, classLoader);
-        return classLoader;
-    }
-
-    private void clearClassLoader(String pkg) {
-        mClassLoaders.remove(pkg);
-    }
-
-    ClassLoader getParentClassLoader() {
-        if (mParentClassLoader == null) {
-            // Lazily load this so it doesn't have any effect on devices without plugins.
-            mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(),
-                    "com.android.systemui.plugin");
-        }
-        return mParentClassLoader;
-    }
-
-    public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException {
-        ClassLoader classLoader = getClassLoader(info.sourceDir, pkg);
-        return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);
-    }
-
-    public static <P> String getAction(Class<P> cls) {
+    static <P> String getAction(Class<P> cls) {
         ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
         if (info == null) {
             throw new RuntimeException(cls + " doesn't provide an interface");
@@ -293,82 +50,4 @@
         }
         return info.action();
     }
-
-    public <T> boolean dependsOn(Plugin p, Class<T> cls) {
-        for (int i = 0; i < mPluginMap.size(); i++) {
-            if (mPluginMap.valueAt(i).dependsOn(p, cls)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @VisibleForTesting
-    public static class PluginInstanceManagerFactory {
-        public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
-                String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
-                Class<?> cls, PluginManager manager) {
-            return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
-                    new VersionInfo().addClass(cls), manager);
-        }
-    }
-
-    // This allows plugins to include any libraries or copied code they want by only including
-    // classes from the plugin library.
-    private static class ClassLoaderFilter extends ClassLoader {
-        private final String mPackage;
-        private final ClassLoader mBase;
-
-        public ClassLoaderFilter(ClassLoader base, String pkg) {
-            super(ClassLoader.getSystemClassLoader());
-            mBase = base;
-            mPackage = pkg;
-        }
-
-        @Override
-        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
-            if (!name.startsWith(mPackage)) super.loadClass(name, resolve);
-            return mBase.loadClass(name);
-        }
-    }
-
-    private class PluginExceptionHandler implements UncaughtExceptionHandler {
-        private final UncaughtExceptionHandler mHandler;
-
-        private PluginExceptionHandler(UncaughtExceptionHandler handler) {
-            mHandler = handler;
-        }
-
-        @Override
-        public void uncaughtException(Thread thread, Throwable throwable) {
-            if (SystemProperties.getBoolean("plugin.debugging", false)) {
-                mHandler.uncaughtException(thread, throwable);
-                return;
-            }
-            // Search for and disable plugins that may have been involved in this crash.
-            boolean disabledAny = checkStack(throwable);
-            if (!disabledAny) {
-                // We couldn't find any plugins involved in this crash, just to be safe
-                // disable all the plugins, so we can be sure that SysUI is running as
-                // best as possible.
-                for (PluginInstanceManager manager : mPluginMap.values()) {
-                    manager.disableAll();
-                }
-            }
-
-            // Run the normal exception handler so we can crash and cleanup our state.
-            mHandler.uncaughtException(thread, throwable);
-        }
-
-        private boolean checkStack(Throwable throwable) {
-            if (throwable == null) return false;
-            boolean disabledAny = false;
-            for (StackTraceElement element : throwable.getStackTrace()) {
-                for (PluginInstanceManager manager : mPluginMap.values()) {
-                    disabledAny |= manager.checkAndDisable(element.getClassName());
-                }
-            }
-            return disabledAny | checkStack(throwable.getCause());
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
new file mode 100644
index 0000000..1fb6c87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.app.Notification;
+import android.app.Notification.Action;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;
+import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import dalvik.system.PathClassLoader;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.Map;
+
+/**
+ * @see Plugin
+ */
+public class PluginManagerImpl extends BroadcastReceiver implements PluginManager {
+
+    static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN";
+
+    private static PluginManager sInstance;
+
+    private final HandlerThread mBackgroundThread;
+    private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
+            = new ArrayMap<>();
+    private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
+    private final ArraySet<String> mOneShotPackages = new ArraySet<>();
+    private final Context mContext;
+    private final PluginInstanceManagerFactory mFactory;
+    private final boolean isDebuggable;
+    private final PluginPrefs mPluginPrefs;
+    private ClassLoaderFilter mParentClassLoader;
+    private boolean mListening;
+    private boolean mHasOneShot;
+
+    public PluginManagerImpl(Context context) {
+        this(context, new PluginInstanceManagerFactory(),
+                Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler());
+    }
+
+    @VisibleForTesting
+    PluginManagerImpl(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
+            UncaughtExceptionHandler defaultHandler) {
+        mContext = context;
+        mFactory = factory;
+        mBackgroundThread = new HandlerThread("Plugins");
+        mBackgroundThread.start();
+        isDebuggable = debuggable;
+        mPluginPrefs = new PluginPrefs(mContext);
+
+        PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
+                defaultHandler);
+        Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
+        if (isDebuggable) {
+            new Handler(mBackgroundThread.getLooper()).post(() -> {
+                // Plugin dependencies that don't have another good home can go here, but
+                // dependencies that have better places to init can happen elsewhere.
+                Dependency.get(PluginDependencyProvider.class)
+                        .allowPluginDependency(ActivityStarter.class);
+            });
+        }
+    }
+
+    public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
+        ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
+        if (info == null) {
+            throw new RuntimeException(cls + " doesn't provide an interface");
+        }
+        if (TextUtils.isEmpty(info.action())) {
+            throw new RuntimeException(cls + " doesn't provide an action");
+        }
+        return getOneShotPlugin(info.action(), cls);
+    }
+
+    public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
+        if (!isDebuggable) {
+            // Never ever ever allow these on production builds, they are only for prototyping.
+            return null;
+        }
+        if (Looper.myLooper() != Looper.getMainLooper()) {
+            throw new RuntimeException("Must be called from UI thread");
+        }
+        PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null,
+                false, mBackgroundThread.getLooper(), cls, this);
+        mPluginPrefs.addAction(action);
+        PluginInfo<T> info = p.getPlugin();
+        if (info != null) {
+            mOneShotPackages.add(info.mPackage);
+            mHasOneShot = true;
+            startListening();
+            return info.mPlugin;
+        }
+        return null;
+    }
+
+    public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {
+        addPluginListener(listener, cls, false);
+    }
+
+    public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
+            boolean allowMultiple) {
+        addPluginListener(PluginManager.getAction(cls), listener, cls, allowMultiple);
+    }
+
+    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            Class<?> cls) {
+        addPluginListener(action, listener, cls, false);
+    }
+
+    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            Class cls, boolean allowMultiple) {
+        if (!isDebuggable) {
+            // Never ever ever allow these on production builds, they are only for prototyping.
+            return;
+        }
+        mPluginPrefs.addAction(action);
+        PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
+                allowMultiple, mBackgroundThread.getLooper(), cls, this);
+        p.loadAll();
+        mPluginMap.put(listener, p);
+        startListening();
+    }
+
+    public void removePluginListener(PluginListener<?> listener) {
+        if (!isDebuggable) {
+            // Never ever ever allow these on production builds, they are only for prototyping.
+            return;
+        }
+        if (!mPluginMap.containsKey(listener)) return;
+        mPluginMap.remove(listener).destroy();
+        stopListening();
+    }
+
+    private void startListening() {
+        if (mListening) return;
+        mListening = true;
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(PLUGIN_CHANGED);
+        filter.addAction(DISABLE_PLUGIN);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(this, filter);
+        filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
+        mContext.registerReceiver(this, filter);
+    }
+
+    private void stopListening() {
+        // Never stop listening if a one-shot is present.
+        if (!mListening || mHasOneShot) return;
+        mListening = false;
+        mContext.unregisterReceiver(this);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+            for (PluginInstanceManager manager : mPluginMap.values()) {
+                manager.loadAll();
+            }
+        } else if (DISABLE_PLUGIN.equals(intent.getAction())) {
+            Uri uri = intent.getData();
+            ComponentName component = ComponentName.unflattenFromString(
+                    uri.toString().substring(10));
+            mContext.getPackageManager().setComponentEnabledSetting(component,
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP);
+            mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(),
+                    SystemMessage.NOTE_PLUGIN);
+        } else {
+            Uri data = intent.getData();
+            String pkg = data.getEncodedSchemeSpecificPart();
+            if (mOneShotPackages.contains(pkg)) {
+                int icon = mContext.getResources().getIdentifier("tuner", "drawable",
+                        mContext.getPackageName());
+                int color = Resources.getSystem().getIdentifier(
+                        "system_notification_accent_color", "color", "android");
+                String label = pkg;
+                try {
+                    PackageManager pm = mContext.getPackageManager();
+                    label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString();
+                } catch (NameNotFoundException e) {
+                }
+                // Localization not required as this will never ever appear in a user build.
+                final Notification.Builder nb =
+                        new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                                .setSmallIcon(icon)
+                                .setWhen(0)
+                                .setShowWhen(false)
+                                .setPriority(Notification.PRIORITY_MAX)
+                                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                                .setColor(mContext.getColor(color))
+                                .setContentTitle("Plugin \"" + label + "\" has updated")
+                                .setContentText("Restart SysUI for changes to take effect.");
+                Intent i = new Intent("com.android.systemui.action.RESTART").setData(
+                            Uri.parse("package://" + pkg));
+                PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
+                nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build());
+                mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg,
+                        SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL);
+            }
+            clearClassLoader(pkg);
+            if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+                for (PluginInstanceManager manager : mPluginMap.values()) {
+                    manager.onPackageChange(pkg);
+                }
+            } else {
+                for (PluginInstanceManager manager : mPluginMap.values()) {
+                    manager.onPackageRemoved(pkg);
+                }
+            }
+        }
+    }
+
+    public ClassLoader getClassLoader(String sourceDir, String pkg) {
+        if (mClassLoaders.containsKey(pkg)) {
+            return mClassLoaders.get(pkg);
+        }
+        ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader());
+        mClassLoaders.put(pkg, classLoader);
+        return classLoader;
+    }
+
+    private void clearClassLoader(String pkg) {
+        mClassLoaders.remove(pkg);
+    }
+
+    ClassLoader getParentClassLoader() {
+        if (mParentClassLoader == null) {
+            // Lazily load this so it doesn't have any effect on devices without plugins.
+            mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(),
+                    "com.android.systemui.plugin");
+        }
+        return mParentClassLoader;
+    }
+
+    public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException {
+        ClassLoader classLoader = getClassLoader(info.sourceDir, pkg);
+        return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);
+    }
+
+    public <T> boolean dependsOn(Plugin p, Class<T> cls) {
+        for (int i = 0; i < mPluginMap.size(); i++) {
+            if (mPluginMap.valueAt(i).dependsOn(p, cls)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    public static class PluginInstanceManagerFactory {
+        public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
+                String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
+                Class<?> cls, PluginManagerImpl manager) {
+            return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
+                    new VersionInfo().addClass(cls), manager);
+        }
+    }
+
+    // This allows plugins to include any libraries or copied code they want by only including
+    // classes from the plugin library.
+    private static class ClassLoaderFilter extends ClassLoader {
+        private final String mPackage;
+        private final ClassLoader mBase;
+
+        public ClassLoaderFilter(ClassLoader base, String pkg) {
+            super(ClassLoader.getSystemClassLoader());
+            mBase = base;
+            mPackage = pkg;
+        }
+
+        @Override
+        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+            if (!name.startsWith(mPackage)) super.loadClass(name, resolve);
+            return mBase.loadClass(name);
+        }
+    }
+
+    private class PluginExceptionHandler implements UncaughtExceptionHandler {
+        private final UncaughtExceptionHandler mHandler;
+
+        private PluginExceptionHandler(UncaughtExceptionHandler handler) {
+            mHandler = handler;
+        }
+
+        @Override
+        public void uncaughtException(Thread thread, Throwable throwable) {
+            if (SystemProperties.getBoolean("plugin.debugging", false)) {
+                mHandler.uncaughtException(thread, throwable);
+                return;
+            }
+            // Search for and disable plugins that may have been involved in this crash.
+            boolean disabledAny = checkStack(throwable);
+            if (!disabledAny) {
+                // We couldn't find any plugins involved in this crash, just to be safe
+                // disable all the plugins, so we can be sure that SysUI is running as
+                // best as possible.
+                for (PluginInstanceManager manager : mPluginMap.values()) {
+                    manager.disableAll();
+                }
+            }
+
+            // Run the normal exception handler so we can crash and cleanup our state.
+            mHandler.uncaughtException(thread, throwable);
+        }
+
+        private boolean checkStack(Throwable throwable) {
+            if (throwable == null) return false;
+            boolean disabledAny = false;
+            for (StackTraceElement element : throwable.getStackTrace()) {
+                for (PluginInstanceManager manager : mPluginMap.values()) {
+                    disabledAny |= manager.checkAndDisable(element.getClassName());
+                }
+            }
+            return disabledAny | checkStack(throwable.getCause());
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index dceeb74..0924089 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -29,7 +29,6 @@
 import android.os.BatteryStats;
 import android.os.Handler;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -95,12 +94,20 @@
     private final DevicePolicyManager mDevicePolicyManager;
     private boolean mDozing;
 
+    /**
+     * Creates a new KeyguardIndicationController and registers callbacks.
+     */
     public KeyguardIndicationController(Context context, ViewGroup indicationArea,
             LockIcon lockIcon) {
         this(context, indicationArea, lockIcon,
                 WakeLock.createPartial(context, "Doze:KeyguardIndication"));
+
+        registerCallbacks(KeyguardUpdateMonitor.getInstance(context));
     }
 
+    /**
+     * Creates a new KeyguardIndicationController for testing. Does *not* register callbacks.
+     */
     @VisibleForTesting
     KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon,
                 WakeLock wakeLock) {
@@ -124,12 +131,15 @@
         mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
 
-        KeyguardUpdateMonitor.getInstance(context).registerCallback(getKeyguardCallback());
-        context.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM,
+        updateDisclosure();
+    }
+
+    private void registerCallbacks(KeyguardUpdateMonitor monitor) {
+        monitor.registerCallback(getKeyguardCallback());
+
+        mContext.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM,
                 new IntentFilter(Intent.ACTION_TIME_TICK), null,
                 Dependency.get(Dependency.TIME_TICK_HANDLER));
-
-        updateDisclosure();
     }
 
     /**
@@ -331,7 +341,7 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
     }
 
-    BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             mHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 7c4f2ee..369ce69 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -1,226 +1,94 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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
+ * 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
+ * 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.tuner;
 
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
 
 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
 import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.util.leak.LeakDetector;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-
-
-public class TunerService {
+public abstract class TunerService {
 
     public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER";
 
-    private static final String TUNER_VERSION = "sysui_tuner_version";
+    public abstract void clearAll();
+    public abstract void destroy();
 
-    private static final int CURRENT_TUNER_VERSION = 1;
+    public abstract String getValue(String setting);
+    public abstract int getValue(String setting, int def);
+    public abstract String getValue(String setting, String def);
 
-    private final Observer mObserver = new Observer();
-    // Map of Uris we listen on to their settings keys.
-    private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>();
-    // Map of settings keys to the listener.
-    private final HashMap<String, Set<Tunable>> mTunableLookup = new HashMap<>();
-    // Set of all tunables, used for leak detection.
-    private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null;
-    private final Context mContext;
+    public abstract void setValue(String setting, String value);
+    public abstract void setValue(String setting, int value);
 
-    private ContentResolver mContentResolver;
-    private int mCurrentUser;
-    private CurrentUserTracker mUserTracker;
+    public abstract void addTunable(Tunable tunable, String... keys);
+    public abstract void removeTunable(Tunable tunable);
 
-    public TunerService(Context context) {
-        mContext = context;
-        mContentResolver = mContext.getContentResolver();
+    public interface Tunable {
+        void onTuningChanged(String key, String newValue);
+    }
 
-        for (UserInfo user : UserManager.get(mContext).getUsers()) {
-            mCurrentUser = user.getUserHandle().getIdentifier();
-            if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
-                upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION);
+    private static Context userContext(Context context) {
+        try {
+            return context.createPackageContextAsUser(context.getPackageName(), 0,
+                    new UserHandle(ActivityManager.getCurrentUser()));
+        } catch (NameNotFoundException e) {
+            return context;
+        }
+    }
+
+    public static final void setTunerEnabled(Context context, boolean enabled) {
+        userContext(context).getPackageManager().setComponentEnabledSetting(
+                new ComponentName(context, TunerActivity.class),
+                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+
+        userContext(context).getPackageManager().setComponentEnabledSetting(
+                new ComponentName(context, TunerActivity.ACTIVITY_ALIAS_NAME),
+                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+    }
+
+    public static final boolean isTunerEnabled(Context context) {
+        return userContext(context).getPackageManager().getComponentEnabledSetting(
+                new ComponentName(context, TunerActivity.class))
+                == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+    }
+
+    public static class ClearReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION_CLEAR.equals(intent.getAction())) {
+                Dependency.get(TunerService.class).clearAll();
             }
         }
-
-        mCurrentUser = ActivityManager.getCurrentUser();
-        mUserTracker = new CurrentUserTracker(mContext) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUser = newUserId;
-                reloadAll();
-                reregisterAll();
-            }
-        };
-        mUserTracker.startTracking();
-    }
-
-    public void destroy() {
-        mUserTracker.stopTracking();
-    }
-
-    private void upgradeTuner(int oldVersion, int newVersion) {
-        if (oldVersion < 1) {
-            String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST);
-            if (blacklistStr != null) {
-                ArraySet<String> iconBlacklist =
-                        StatusBarIconController.getIconBlacklist(blacklistStr);
-
-                iconBlacklist.add("rotate");
-                iconBlacklist.add("headset");
-
-                Settings.Secure.putStringForUser(mContentResolver,
-                        StatusBarIconController.ICON_BLACKLIST,
-                        TextUtils.join(",", iconBlacklist), mCurrentUser);
-            }
-        }
-        setValue(TUNER_VERSION, newVersion);
-    }
-
-    public String getValue(String setting) {
-        return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
-    }
-
-    public void setValue(String setting, String value) {
-         Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser);
-    }
-
-    public int getValue(String setting, int def) {
-        return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser);
-    }
-
-    public String getValue(String setting, String def) {
-        String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
-        if (ret == null) return def;
-        return ret;
-    }
-
-    public void setValue(String setting, int value) {
-         Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser);
-    }
-
-    public void addTunable(Tunable tunable, String... keys) {
-        for (String key : keys) {
-            addTunable(tunable, key);
-        }
-    }
-
-    private void addTunable(Tunable tunable, String key) {
-        if (!mTunableLookup.containsKey(key)) {
-            mTunableLookup.put(key, new ArraySet<Tunable>());
-        }
-        mTunableLookup.get(key).add(tunable);
-        if (LeakDetector.ENABLED) {
-            mTunables.add(tunable);
-            Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables");
-        }
-        Uri uri = Settings.Secure.getUriFor(key);
-        if (!mListeningUris.containsKey(uri)) {
-            mListeningUris.put(uri, key);
-            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
-        }
-        // Send the first state.
-        String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
-        tunable.onTuningChanged(key, value);
-    }
-
-    public void removeTunable(Tunable tunable) {
-        for (Set<Tunable> list : mTunableLookup.values()) {
-            list.remove(tunable);
-        }
-        if (LeakDetector.ENABLED) {
-            mTunables.remove(tunable);
-        }
-    }
-
-    protected void reregisterAll() {
-        if (mListeningUris.size() == 0) {
-            return;
-        }
-        mContentResolver.unregisterContentObserver(mObserver);
-        for (Uri uri : mListeningUris.keySet()) {
-            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
-        }
-    }
-
-    public void reloadSetting(Uri uri) {
-        String key = mListeningUris.get(uri);
-        Set<Tunable> tunables = mTunableLookup.get(key);
-        if (tunables == null) {
-            return;
-        }
-        String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
-        for (Tunable tunable : tunables) {
-            tunable.onTuningChanged(key, value);
-        }
-    }
-
-    private void reloadAll() {
-        for (String key : mTunableLookup.keySet()) {
-            String value = Settings.Secure.getStringForUser(mContentResolver, key,
-                    mCurrentUser);
-            for (Tunable tunable : mTunableLookup.get(key)) {
-                tunable.onTuningChanged(key, value);
-            }
-        }
-    }
-
-    public void clearAll() {
-        // A couple special cases.
-        Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null);
-        Settings.System.putString(mContentResolver,
-                SHOW_BATTERY_PERCENT, null);
-        Intent intent = new Intent(DemoMode.ACTION_DEMO);
-        intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
-        mContext.sendBroadcast(intent);
-
-        for (String key : mTunableLookup.keySet()) {
-            Settings.Secure.putString(mContentResolver, key, null);
-        }
     }
 
     public static final void showResetRequest(final Context context, final Runnable onDisabled) {
@@ -247,59 +115,4 @@
         });
         dialog.show();
     }
-
-    public static final void setTunerEnabled(Context context, boolean enabled) {
-        userContext(context).getPackageManager().setComponentEnabledSetting(
-                new ComponentName(context, TunerActivity.class),
-                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                        PackageManager.DONT_KILL_APP);
-
-        userContext(context).getPackageManager().setComponentEnabledSetting(
-                new ComponentName(context, TunerActivity.ACTIVITY_ALIAS_NAME),
-                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                PackageManager.DONT_KILL_APP);
-    }
-
-    public static final boolean isTunerEnabled(Context context) {
-        return userContext(context).getPackageManager().getComponentEnabledSetting(
-                new ComponentName(context, TunerActivity.class))
-                == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-    }
-
-    private static Context userContext(Context context) {
-        try {
-            return context.createPackageContextAsUser(context.getPackageName(), 0,
-                    new UserHandle(ActivityManager.getCurrentUser()));
-        } catch (NameNotFoundException e) {
-            return context;
-        }
-    }
-
-    private class Observer extends ContentObserver {
-        public Observer() {
-            super(new Handler(Looper.getMainLooper()));
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri, int userId) {
-            if (userId == ActivityManager.getCurrentUser()) {
-                reloadSetting(uri);
-            }
-        }
-    }
-
-    public interface Tunable {
-        void onTuningChanged(String key, String newValue);
-    }
-
-    public static class ClearReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (ACTION_CLEAR.equals(intent.getAction())) {
-                Dependency.get(TunerService.class).clearAll();
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
new file mode 100644
index 0000000..8e584bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 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.tuner;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+
+public class TunerServiceImpl extends TunerService {
+
+    private static final String TUNER_VERSION = "sysui_tuner_version";
+
+    private static final int CURRENT_TUNER_VERSION = 1;
+
+    private final Observer mObserver = new Observer();
+    // Map of Uris we listen on to their settings keys.
+    private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>();
+    // Map of settings keys to the listener.
+    private final HashMap<String, Set<Tunable>> mTunableLookup = new HashMap<>();
+    // Set of all tunables, used for leak detection.
+    private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null;
+    private final Context mContext;
+
+    private ContentResolver mContentResolver;
+    private int mCurrentUser;
+    private CurrentUserTracker mUserTracker;
+
+    public TunerServiceImpl(Context context) {
+        mContext = context;
+        mContentResolver = mContext.getContentResolver();
+
+        for (UserInfo user : UserManager.get(mContext).getUsers()) {
+            mCurrentUser = user.getUserHandle().getIdentifier();
+            if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
+                upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION);
+            }
+        }
+
+        mCurrentUser = ActivityManager.getCurrentUser();
+        mUserTracker = new CurrentUserTracker(mContext) {
+            @Override
+            public void onUserSwitched(int newUserId) {
+                mCurrentUser = newUserId;
+                reloadAll();
+                reregisterAll();
+            }
+        };
+        mUserTracker.startTracking();
+    }
+
+    @Override
+    public void destroy() {
+        mUserTracker.stopTracking();
+    }
+
+    private void upgradeTuner(int oldVersion, int newVersion) {
+        if (oldVersion < 1) {
+            String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST);
+            if (blacklistStr != null) {
+                ArraySet<String> iconBlacklist =
+                        StatusBarIconController.getIconBlacklist(blacklistStr);
+
+                iconBlacklist.add("rotate");
+                iconBlacklist.add("headset");
+
+                Settings.Secure.putStringForUser(mContentResolver,
+                        StatusBarIconController.ICON_BLACKLIST,
+                        TextUtils.join(",", iconBlacklist), mCurrentUser);
+            }
+        }
+        setValue(TUNER_VERSION, newVersion);
+    }
+
+    @Override
+    public String getValue(String setting) {
+        return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
+    }
+
+    @Override
+    public void setValue(String setting, String value) {
+         Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser);
+    }
+
+    @Override
+    public int getValue(String setting, int def) {
+        return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser);
+    }
+
+    @Override
+    public String getValue(String setting, String def) {
+        String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
+        if (ret == null) return def;
+        return ret;
+    }
+
+    @Override
+    public void setValue(String setting, int value) {
+         Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser);
+    }
+
+    @Override
+    public void addTunable(Tunable tunable, String... keys) {
+        for (String key : keys) {
+            addTunable(tunable, key);
+        }
+    }
+
+    private void addTunable(Tunable tunable, String key) {
+        if (!mTunableLookup.containsKey(key)) {
+            mTunableLookup.put(key, new ArraySet<Tunable>());
+        }
+        mTunableLookup.get(key).add(tunable);
+        if (LeakDetector.ENABLED) {
+            mTunables.add(tunable);
+            Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables");
+        }
+        Uri uri = Settings.Secure.getUriFor(key);
+        if (!mListeningUris.containsKey(uri)) {
+            mListeningUris.put(uri, key);
+            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
+        }
+        // Send the first state.
+        String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+        tunable.onTuningChanged(key, value);
+    }
+
+    @Override
+    public void removeTunable(Tunable tunable) {
+        for (Set<Tunable> list : mTunableLookup.values()) {
+            list.remove(tunable);
+        }
+        if (LeakDetector.ENABLED) {
+            mTunables.remove(tunable);
+        }
+    }
+
+    protected void reregisterAll() {
+        if (mListeningUris.size() == 0) {
+            return;
+        }
+        mContentResolver.unregisterContentObserver(mObserver);
+        for (Uri uri : mListeningUris.keySet()) {
+            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
+        }
+    }
+
+    private void reloadSetting(Uri uri) {
+        String key = mListeningUris.get(uri);
+        Set<Tunable> tunables = mTunableLookup.get(key);
+        if (tunables == null) {
+            return;
+        }
+        String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+        for (Tunable tunable : tunables) {
+            tunable.onTuningChanged(key, value);
+        }
+    }
+
+    private void reloadAll() {
+        for (String key : mTunableLookup.keySet()) {
+            String value = Settings.Secure.getStringForUser(mContentResolver, key,
+                    mCurrentUser);
+            for (Tunable tunable : mTunableLookup.get(key)) {
+                tunable.onTuningChanged(key, value);
+            }
+        }
+    }
+
+    @Override
+    public void clearAll() {
+        // A couple special cases.
+        Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null);
+        Intent intent = new Intent(DemoMode.ACTION_DEMO);
+        intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
+        mContext.sendBroadcast(intent);
+
+        for (String key : mTunableLookup.keySet()) {
+            Settings.Secure.putString(mContentResolver, key, null);
+        }
+    }
+
+    private class Observer extends ContentObserver {
+        public Observer() {
+            super(new Handler(Looper.getMainLooper()));
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri, int userId) {
+            if (userId == ActivityManager.getCurrentUser()) {
+                reloadSetting(uri);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 760d875..576299f 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -49,7 +49,8 @@
     mockito-updated-target-minus-junit4 \
     SystemUI-proto \
     SystemUI-tags \
-    legacy-android-test
+    legacy-android-test \
+    testables
 
 LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 27955ec..c297ae8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -39,21 +39,21 @@
     @Test
     public void testClassDependency() {
         FlashlightController f = mock(FlashlightController.class);
-        injectTestDependency(FlashlightController.class, f);
+        mDependency.injectTestDependency(FlashlightController.class, f);
         Assert.assertEquals(f, Dependency.get(FlashlightController.class));
     }
 
     @Test
     public void testStringDependency() {
         Looper l = Looper.getMainLooper();
-        injectTestDependency(Dependency.BG_LOOPER, l);
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, l);
         assertEquals(l, Dependency.get(Dependency.BG_LOOPER));
     }
 
     @Test
     public void testDump() {
         Dumpable d = mock(Dumpable.class);
-        injectTestDependency(DUMPABLE, d);
+        mDependency.injectTestDependency(DUMPABLE, d);
         Dependency.get(DUMPABLE);
         mDependency.dump(null, mock(PrintWriter.class), null);
         verify(d).dump(eq(null), any(), eq(null));
@@ -62,7 +62,7 @@
     @Test
     public void testConfigurationChanged() {
         ConfigurationChangedReceiver d = mock(ConfigurationChangedReceiver.class);
-        injectTestDependency(CONFIGURATION_CHANGED_RECEIVER, d);
+        mDependency.injectTestDependency(CONFIGURATION_CHANGED_RECEIVER, d);
         Dependency.get(CONFIGURATION_CHANGED_RECEIVER);
         mDependency.onConfigurationChanged(null);
         verify(d).onConfigurationChanged(eq(null));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
new file mode 100644
index 0000000..15cebc7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import android.app.Fragment;
+import android.support.test.InstrumentationRegistry;
+import android.testing.BaseFragmentTest;
+
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+import com.android.systemui.utils.leaks.LeakCheckedTest.SysuiLeakCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+
+public abstract class SysuiBaseFragmentTest extends BaseFragmentTest {
+
+    public static final Class<?>[] ALL_SUPPORTED_CLASSES = LeakCheckedTest.ALL_SUPPORTED_CLASSES;
+
+    @Rule
+    public final SysuiLeakCheck mLeakCheck = new SysuiLeakCheck();
+
+    protected final TestableDependency mDependency = new TestableDependency(mContext);
+    protected SysuiTestableContext mSysuiContext;
+
+    public SysuiBaseFragmentTest(Class<? extends Fragment> cls) {
+        super(cls);
+    }
+
+    @Before
+    public void SysuiSetup() {
+        System.setProperty("dexmaker.share_classloader", "true");
+        SystemUIFactory.createFromConfig(mContext);
+        // TODO: Figure out another way to give reference to a SysuiTestableContext.
+        mSysuiContext = (SysuiTestableContext) mContext;
+    }
+
+    @Override
+    protected SysuiTestableContext getContext() {
+        return new SysuiTestableContext(InstrumentationRegistry.getContext(), mLeakCheck);
+    }
+
+    public void injectLeakCheckedDependencies(Class<?>... cls) {
+        for (Class<?> c : cls) {
+            injectLeakCheckedDependency(c);
+        }
+    }
+
+    public <T> void injectLeakCheckedDependency(Class<T> c) {
+        mDependency.injectTestDependency(c, mLeakCheck.getLeakChecker(c));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index c0e7e80..aadae0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -15,49 +15,38 @@
  */
 package com.android.systemui;
 
-import static org.mockito.Mockito.mock;
-
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.support.test.InstrumentationRegistry;
-import android.util.ArrayMap;
+import android.testing.LeakCheck;
 
-import com.android.systemui.Dependency.DependencyKey;
-import com.android.systemui.utils.TestableContext;
-import com.android.systemui.utils.leaks.Tracker;
-
-import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 
 /**
  * Base class that does System UI specific setup.
  */
 public abstract class SysuiTestCase {
 
-    private Throwable mException;
     private Handler mHandler;
-    protected TestableContext mContext;
-    protected TestDependency mDependency;
+    @Rule
+    public SysuiTestableContext mContext = new SysuiTestableContext(
+            InstrumentationRegistry.getContext(), getLeakCheck());
+    public TestableDependency mDependency = new TestableDependency(mContext);
 
     @Before
     public void SysuiSetup() throws Exception {
-        mException = null;
         System.setProperty("dexmaker.share_classloader", "true");
-        mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this);
         SystemUIFactory.createFromConfig(mContext);
-        mDependency = new TestDependency();
-        mDependency.mContext = mContext;
-        mDependency.start();
     }
 
-    @After
-    public void cleanup() throws Exception {
-        mContext.getSettingsProvider().clearOverrides(this);
+    protected LeakCheck getLeakCheck() {
+        return null;
     }
 
-    protected Context getContext() {
+    public Context getContext() {
         return mContext;
     }
 
@@ -84,48 +73,11 @@
         }
     }
 
-    // Used for leak tracking, returns null to indicate no leak tracking by default.
-    public Tracker getTracker(String tag) {
-        return null;
-    }
-
-    public <T> T injectMockDependency(Class<T> cls) {
-        final T mock = mock(cls);
-        mDependency.injectTestDependency(cls, mock);
-        return mock;
-    }
-
-    public <T> void injectTestDependency(Class<T> cls, T obj) {
-        mDependency.injectTestDependency(cls, obj);
-    }
-
-    public <T> void injectTestDependency(DependencyKey<T> key, T obj) {
-        mDependency.injectTestDependency(key, obj);
-    }
-
     public static final class EmptyRunnable implements Runnable {
         public void run() {
         }
     }
 
-    public static class TestDependency extends Dependency {
-        private final ArrayMap<Object, Object> mObjs = new ArrayMap<>();
-
-        private <T> void injectTestDependency(DependencyKey<T> key, T obj) {
-            mObjs.put(key, obj);
-        }
-
-        private <T> void injectTestDependency(Class<T> key, T obj) {
-            mObjs.put(key, obj);
-        }
-
-        @Override
-        protected <T> T createDependency(Object key) {
-            if (mObjs.containsKey(key)) return (T) mObjs.get(key);
-            return super.createDependency(key);
-        }
-    }
-
     public static final class Idler implements MessageQueue.IdleHandler {
         private final Runnable mCallback;
         private boolean mIdle;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java
new file mode 100644
index 0000000..b94a2fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.testing.LeakCheck;
+import android.testing.TestableContext;
+import android.util.ArrayMap;
+
+public class SysuiTestableContext extends TestableContext implements SysUiServiceProvider {
+
+    private ArrayMap<Class<?>, Object> mComponents;
+
+    public SysuiTestableContext(Context base) {
+        super(base);
+    }
+
+    public SysuiTestableContext(Context base, LeakCheck check) {
+        super(base, check);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> T getComponent(Class<T> interfaceType) {
+        return (T) (mComponents != null ? mComponents.get(interfaceType) : null);
+    }
+
+    public <T, C extends T> void putComponent(Class<T> interfaceType, C component) {
+        if (mComponents == null) mComponents = new ArrayMap<>();
+        mComponents.put(interfaceType, component);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
new file mode 100644
index 0000000..53a7994
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.util.ArrayMap;
+
+public class TestableDependency extends Dependency {
+    private final ArrayMap<Object, Object> mObjs = new ArrayMap<>();
+
+    public TestableDependency(Context context) {
+        mContext = context;
+        if (SystemUIFactory.getInstance() == null) {
+            SystemUIFactory.createFromConfig(context);
+        }
+        start();
+    }
+
+    public <T> T injectMockDependency(Class<T> cls) {
+        final T mock = mock(cls);
+        injectTestDependency(cls, mock);
+        return mock;
+    }
+
+    public <T> void injectTestDependency(DependencyKey<T> key, T obj) {
+        mObjs.put(key, obj);
+    }
+
+    public <T> void injectTestDependency(Class<T> key, T obj) {
+        mObjs.put(key, obj);
+    }
+
+    @Override
+    protected <T> T createDependency(Object key) {
+        if (mObjs.containsKey(key)) return (T) mObjs.get(key);
+        return super.createDependency(key);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java
index 5477afa8..048936b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java
@@ -47,7 +47,7 @@
             return;
         }
 
-        mContext.getSettingsProvider().acquireOverridesBuilder(this)
+        mContext.getSettingsProvider().acquireOverridesBuilder()
                 .addSetting("secure", Settings.Secure.DOZE_ALWAYS_ON, null)
                 .build();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index ba39671..cdbde5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -39,19 +39,20 @@
 import static org.mockito.Mockito.when;
 
 import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
 import android.view.Display;
 
-import com.android.systemui.SysUIRunner;
-import com.android.systemui.UiThreadTest;
 import com.android.internal.hardware.AmbientDisplayConfiguration;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
+import android.testing.UiThreadTest;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @UiThreadTest
 public class DozeMachineTest {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java
index 53053fa..8484bed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java
@@ -14,22 +14,25 @@
 
 package com.android.systemui.notification;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-
+import android.testing.AndroidTestingRunner;
+import android.testing.UiThreadTest;
 import android.util.FloatProperty;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.systemui.SysUIRunner;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.UiThreadTest;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.stack.AnimationFilter;
 import com.android.systemui.statusbar.stack.AnimationProperties;
@@ -39,18 +42,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @SmallTest
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @UiThreadTest
 public class PropertyAnimatorTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
index 658966c..4f0815d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
@@ -72,7 +72,7 @@
     private PackageManager mMockPm;
     private PluginListener mMockListener;
     private PluginInstanceManager mPluginInstanceManager;
-    private PluginManager mMockManager;
+    private PluginManagerImpl mMockManager;
     private VersionInfo mMockVersionInfo;
 
     @Before
@@ -82,7 +82,7 @@
         mContextWrapper = new MyContextWrapper(getContext());
         mMockPm = mock(PackageManager.class);
         mMockListener = mock(PluginListener.class);
-        mMockManager = mock(PluginManager.class);
+        mMockManager = mock(PluginManagerImpl.class);
         when(mMockManager.getClassLoader(any(), any()))
                 .thenReturn(getClass().getClassLoader());
         mMockVersionInfo = mock(VersionInfo.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
index 053e5cf2..a3d5d5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -34,7 +34,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
-import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory;
+import com.android.systemui.plugins.PluginManagerImpl.PluginInstanceManagerFactory;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,7 +50,7 @@
 
     private PluginInstanceManagerFactory mMockFactory;
     private PluginInstanceManager mMockPluginInstance;
-    private PluginManager mPluginManager;
+    private PluginManagerImpl mPluginManager;
     private PluginListener mMockListener;
 
     private UncaughtExceptionHandler mRealExceptionHandler;
@@ -66,7 +66,8 @@
         when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(),
                 Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any()))
                 .thenReturn(mMockPluginInstance);
-        mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler);
+        mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true,
+                mMockExceptionHandler);
         resetExceptionHandler();
         mMockListener = mock(PluginListener.class);
     }
@@ -98,7 +99,7 @@
 
     @Test
     public void testNonDebuggable() {
-        mPluginManager = new PluginManager(getContext(), mMockFactory, false,
+        mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false,
                 mMockExceptionHandler);
         resetExceptionHandler();
 
@@ -148,7 +149,7 @@
 
         ComponentName testComponent = new ComponentName(getContext().getPackageName(),
                 PluginManagerTest.class.getName());
-        Intent intent = new Intent(PluginManager.DISABLE_PLUGIN);
+        Intent intent = new Intent(PluginManagerImpl.DISABLE_PLUGIN);
         intent.setData(Uri.parse("package://" + testComponent.flattenToString()));
         mPluginManager.onReceive(mContext, intent);
         verify(nm).cancel(eq(testComponent.getClassName()), eq(SystemMessage.NOTE_PLUGIN));
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 7153340..deb31da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -21,14 +21,16 @@
 
 import com.android.keyguard.CarrierText;
 import com.android.systemui.Dependency;
-import com.android.systemui.FragmentTestCase;
 import com.android.systemui.R;
-import com.android.systemui.SysUIRunner;
+
+import android.testing.AndroidTestingRunner;
+
+import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.util.LayoutInflaterBuilder;
-import com.android.systemui.utils.TestableLooper;
-import com.android.systemui.utils.TestableLooper.RunWithLooper;
+import android.testing.LayoutInflaterBuilder;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -38,9 +40,9 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
-public class QSFragmentTest extends FragmentTestCase {
+public class QSFragmentTest extends SysuiBaseFragmentTest {
 
     public QSFragmentTest() {
         super(QSFragment.class);
@@ -56,8 +58,9 @@
                         .replace(CarrierText.class, View.class)
                         .build());
 
-        injectTestDependency(Dependency.BG_LOOPER, TestableLooper.get(this).getLooper());
-        injectMockDependency(UserSwitcherController.class);
+        mDependency.injectTestDependency(Dependency.BG_LOOPER,
+                TestableLooper.get(this).getLooper());
+        mDependency.injectMockDependency(UserSwitcherController.class);
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index e38c30f..1ff373c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -33,8 +33,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.util.LayoutInflaterBuilder;
-import com.android.systemui.utils.TestableImageView;
+import android.testing.LayoutInflaterBuilder;
+import android.testing.TestableImageView;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -57,8 +57,8 @@
 
     @Before
     public void setUp() {
-        injectTestDependency(SecurityController.class, mSecurityController);
-        injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
+        mDependency.injectTestDependency(SecurityController.class, mSecurityController);
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
                 new LayoutInflaterBuilder(mContext)
                         .replace("ImageView", TestableImageView.class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index d1e17f4..5ae107a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -21,13 +21,13 @@
 import static org.mockito.Mockito.when;
 
 import com.android.systemui.Dependency;
-import com.android.systemui.SysUIRunner;
+import android.testing.AndroidTestingRunner;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.utils.TestableLooper;
-import com.android.systemui.utils.TestableLooper.RunWithLooper;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -37,7 +37,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 @SmallTest
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class TileQueryHelperTest extends SysuiTestCase {
     private TestableLooper mBGLooper;
@@ -46,7 +46,7 @@
     @Before
     public void setup() {
         mBGLooper = TestableLooper.get(this);
-        injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper());
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 6d7b50f..ddd6615 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -25,12 +25,12 @@
 import android.service.quicksettings.Tile;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.systemui.SysUIRunner;
+import android.testing.AndroidTestingRunner;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.utils.TestableLooper;
-import com.android.systemui.utils.TestableLooper.RunWithLooper;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.After;
 import org.junit.Before;
@@ -42,7 +42,7 @@
 import java.util.ArrayList;
 
 @SmallTest
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
 public class TileServicesTest extends SysuiTestCase {
     private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 4250962..bdd05c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar;
 
-import static android.support.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
@@ -34,19 +32,17 @@
 import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
-import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -125,7 +121,7 @@
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
         createController();
 
-        final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        final KeyguardUpdateMonitorCallback monitor = mController.getKeyguardCallback();
         reset(mDisclosure);
 
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
@@ -176,7 +172,6 @@
         assertFalse(mWakeLock.isHeld());
     }
 
-    @Ignore("Flaky")
     @Test
     public void transientIndication_releasesWakeLock_afterHidingDelayed() throws Throwable {
         mInstrumentation.runOnMainSync(() -> {
@@ -188,12 +183,10 @@
         });
         mInstrumentation.waitForIdleSync();
 
-        boolean[] held = new boolean[2];
+        Boolean[] held = new Boolean[1];
         mInstrumentation.runOnMainSync(() -> {
             held[0] = mWakeLock.isHeld();
-            held[1] = true;
         });
-        assertFalse("wake lock still held", held[0]);
-        assertTrue("held was not written yet", held[1]);
+        assertFalse("WakeLock expected: RELEASED, was: HELD", held[0]);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 8520bdb..5b9270d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -33,41 +33,35 @@
 import static org.mockito.Mockito.when;
 
 import android.app.INotificationManager;
-import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
 import android.graphics.drawable.Drawable;
 import android.service.notification.StatusBarNotification;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
+import android.testing.AndroidTestingRunner;
+import android.testing.UiThreadTest;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.Switch;
 import android.widget.TextView;
-import com.android.internal.util.CharSequences;
+
 import com.android.systemui.R;
-import com.android.systemui.SysUIRunner;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.UiThreadTest;
 
 import org.junit.Before;
-import org.junit.runner.RunWith;
 import org.junit.Test;
-import org.mockito.Mockito;
-import java.util.Arrays;
+import org.junit.runner.RunWith;
+
 import java.util.Collections;
 import java.util.concurrent.CountDownLatch;
 
 @SmallTest
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @UiThreadTest
 public class NotificationInfoTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test_package";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
index b8be4fa..c2c6336 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
@@ -14,17 +14,17 @@
 
 package com.android.systemui.statusbar;
 
-import com.android.systemui.SysUIRunner;
-import com.android.systemui.utils.TestableLooper;
-import com.android.systemui.utils.TestableLooper.RunWithLooper;
-import com.android.systemui.utils.ViewUtils;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.ViewUtils;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
 public class NotificationMenuRowTest extends LeakCheckedTest {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 3ccb160..e41a827 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -22,7 +22,7 @@
 import com.android.internal.app.NightDisplayController;
 import com.android.systemui.Prefs;
 import com.android.systemui.Prefs.Key;
-import com.android.systemui.SysUIRunner;
+import android.testing.AndroidTestingRunner;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.qs.QSTileHost;
 
@@ -32,7 +32,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 public class AutoTileManagerTest extends SysuiTestCase {
 
     private QSTileHost mQsTileHost;
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 f55115e0..a9acda3 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
@@ -23,24 +23,22 @@
 import android.view.View;
 import android.view.ViewPropertyAnimator;
 
-import com.android.systemui.FragmentTestCase;
 import com.android.systemui.R;
-import com.android.systemui.SysUIRunner;
+import android.testing.AndroidTestingRunner;
+
+import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.tuner.TunerService;
-import com.android.systemui.utils.TestableLooper.RunWithLooper;
+import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 import org.mockito.Mockito;
 
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
-public class CollapsedStatusBarFragmentTest extends FragmentTestCase {
+public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
 
     private NotificationIconAreaController mMockNotificiationAreaController;
     private View mNotificationAreaInner;
@@ -51,9 +49,9 @@
 
     @Before
     public void setup() {
-        mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
-        mContext.putComponent(StatusBar.class, mock(StatusBar.class));
-        mContext.putComponent(TunerService.class, mock(TunerService.class));
+        mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+        mSysuiContext.putComponent(StatusBar.class, mock(StatusBar.class));
+        mSysuiContext.putComponent(TunerService.class, mock(TunerService.class));
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         mMockNotificiationAreaController = mock(NotificationIconAreaController.class);
         mNotificationAreaInner = mock(View.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 1fa9846..e7cdcb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -19,24 +19,25 @@
 
 import android.content.Context;
 import android.os.Looper;
+import android.testing.AndroidTestingRunner;
 import android.view.Display;
 import android.view.WindowManager;
 
 import com.android.systemui.Dependency;
-import com.android.systemui.FragmentTestCase;
-import com.android.systemui.SysUIRunner;
+
+import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.utils.TestableLooper.RunWithLooper;
+import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
-public class NavigationBarFragmentTest extends FragmentTestCase {
+public class NavigationBarFragmentTest extends SysuiBaseFragmentTest {
 
     public NavigationBarFragmentTest() {
         super(NavigationBarFragment.class);
@@ -44,11 +45,11 @@
 
     @Before
     public void setup() {
-        injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
-        mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
-        mContext.putComponent(StatusBar.class, mock(StatusBar.class));
-        mContext.putComponent(Recents.class, mock(Recents.class));
-        mContext.putComponent(Divider.class, mock(Divider.class));
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
+        mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+        mSysuiContext.putComponent(StatusBar.class, mock(StatusBar.class));
+        mSysuiContext.putComponent(Recents.class, mock(Recents.class));
+        mSysuiContext.putComponent(Divider.class, mock(Divider.class));
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         WindowManager windowManager = mock(WindowManager.class);
         Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java
index e3a5ef0..3e79a04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java
@@ -42,8 +42,8 @@
 
     @Before
     public void setup() {
-        mPluginManager = injectMockDependency(PluginManager.class);
-        mTunerService = injectMockDependency(TunerService.class);
+        mPluginManager = mDependency.injectMockDependency(PluginManager.class);
+        mTunerService = mDependency.injectMockDependency(TunerService.class);
         mExtensionController = Dependency.get(ExtensionController.class);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 8cbf95b..efa232b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -18,7 +18,7 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider;
+import android.testing.TestableSettings.SettingOverrider;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -92,9 +92,9 @@
                         attr);
 
         // Must set the Settings value before instantiating the NetworkControllerImpl due to bugs in
-        // FakeSettingsProvider.
+        // TestableSettings.
         SettingOverrider settingsOverrider =
-                mContext.getSettingsProvider().acquireOverridesBuilder(this)
+                mContext.getSettingsProvider().acquireOverridesBuilder()
                         .addSetting("global", Settings.Global.NETWORK_SCORING_UI_ENABLED, "1")
                         .build();
         super.setUp(); // re-instantiate NetworkControllImpl now that setting has been updated
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java
deleted file mode 100644
index f40fe4c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.utils;
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
-import android.test.mock.MockContentProvider;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider.Builder;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Allows calls to android.provider.Settings to be tested easier.  A SettingOverride
- * can be acquired and a set of specific settings can be set to a value (and not changed
- * in the system when set), so that they can be tested without breaking the test device.
- * <p>
- * To use, in the before method acquire the override add all settings that will affect if
- * your test passes or not.
- *
- * <pre class="prettyprint">
- * {@literal
- * mSettingOverride = mTestableContext.getSettingsProvider().acquireOverridesBuilder()
- *         .addSetting("secure", Secure.USER_SETUP_COMPLETE, "0")
- *         .build();
- * }
- * </pre>
- *
- * Then in the after free up the settings.
- *
- * <pre class="prettyprint">
- * {@literal
- * mSettingOverride.release();
- * }
- * </pre>
- */
-public class FakeSettingsProvider extends MockContentProvider {
-
-    private static final String TAG = "FakeSettingsProvider";
-    private static final boolean DEBUG = false;
-
-    // Number of times to try to acquire a setting if in use.
-    private static final int MAX_TRIES = 10;
-    // Time to wait for each setting.  WAIT_TIMEOUT * MAX_TRIES will be the maximum wait time
-    // for a setting.
-    private static final long WAIT_TIMEOUT = 1000;
-
-    private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>();
-    private final Map<SysuiTestCase, List<SettingOverrider>> mOwners = new ArrayMap<>();
-
-    private static FakeSettingsProvider sInstance;
-    private final ContentProviderClient mSettings;
-    private final ContentResolver mResolver;
-
-    private FakeSettingsProvider(ContentProviderClient settings, ContentResolver resolver) {
-        mSettings = settings;
-        mResolver = resolver;
-    }
-
-    public Builder acquireOverridesBuilder(SysuiTestCase test) {
-        return new Builder(this, test);
-    }
-
-    public void clearOverrides(SysuiTestCase test) {
-        List<SettingOverrider> overrides = mOwners.remove(test);
-        if (overrides != null) {
-            overrides.forEach(override -> override.ensureReleased());
-        }
-    }
-
-    public Bundle call(String method, String arg, Bundle extras) {
-        // Methods are "GET_system", "GET_global", "PUT_secure", etc.
-        final String[] commands = method.split("_", 2);
-        final String op = commands[0];
-        final String table = commands[1];
-
-        synchronized (mOverrideMap) {
-            SettingOverrider overrider = mOverrideMap.get(key(table, arg));
-            if (overrider == null) {
-                // Fall through to real settings.
-                try {
-                    if (DEBUG) Log.d(TAG, "Falling through to real settings " + method);
-                    // TODO: Add our own version of caching to handle this.
-                    Bundle call = mSettings.call(method, arg, extras);
-                    call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY);
-                    return call;
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-            String value;
-            Bundle out = new Bundle();
-            switch (op) {
-                case "GET":
-                    value = overrider.get(table, arg);
-                    if (value != null) {
-                        out.putString(Settings.NameValueTable.VALUE, value);
-                    }
-                    break;
-                case "PUT":
-                    value = extras.getString(Settings.NameValueTable.VALUE, null);
-                    if (value != null) {
-                        overrider.put(table, arg, value);
-                    } else {
-                        overrider.remove(table, arg);
-                    }
-                    break;
-                default:
-                    throw new UnsupportedOperationException("Unknown command " + method);
-            }
-            return out;
-        }
-    }
-
-    private void acquireSettings(SettingOverrider overridder, Set<String> keys,
-            SysuiTestCase owner) throws AcquireTimeoutException {
-        synchronized (mOwners) {
-            List<SettingOverrider> list = mOwners.get(owner);
-            if (list == null) {
-                list = new ArrayList<>();
-                mOwners.put(owner, list);
-            }
-            list.add(overridder);
-        }
-        synchronized (mOverrideMap) {
-            for (int i = 0; i < MAX_TRIES; i++) {
-                if (checkKeys(keys, false)) break;
-                try {
-                    if (DEBUG) Log.d(TAG, "Waiting for contention to finish");
-                    mOverrideMap.wait(WAIT_TIMEOUT);
-                } catch (InterruptedException e) {
-                }
-            }
-            checkKeys(keys, true);
-            for (String key : keys) {
-                if (DEBUG) Log.d(TAG, "Acquiring " + key);
-                mOverrideMap.put(key, overridder);
-            }
-        }
-    }
-
-    private void releaseSettings(Set<String> keys) {
-        synchronized (mOverrideMap) {
-            for (String key : keys) {
-                if (DEBUG) Log.d(TAG, "Releasing " + key);
-                mOverrideMap.remove(key);
-            }
-            if (DEBUG) Log.d(TAG, "Notifying");
-            mOverrideMap.notify();
-        }
-    }
-
-    @VisibleForTesting
-    public Object getLock() {
-        return mOverrideMap;
-    }
-
-    private boolean checkKeys(Set<String> keys, boolean shouldThrow)
-            throws AcquireTimeoutException {
-        for (String key : keys) {
-            if (mOverrideMap.containsKey(key)) {
-                if (shouldThrow) {
-                    throw new AcquireTimeoutException("Could not acquire " + key);
-                }
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public static class SettingOverrider {
-        private final Set<String> mValidKeys;
-        private final Map<String, String> mValueMap = new ArrayMap<>();
-        private final FakeSettingsProvider mProvider;
-        private boolean mReleased;
-
-        private SettingOverrider(Set<String> keys, FakeSettingsProvider provider) {
-            mValidKeys = new ArraySet<>(keys);
-            mProvider = provider;
-        }
-
-        private void ensureReleased() {
-            if (!mReleased) {
-                release();
-            }
-        }
-
-        public void release() {
-            mProvider.releaseSettings(mValidKeys);
-            mReleased = true;
-        }
-
-        private void putDirect(String key, String value) {
-            mValueMap.put(key, value);
-        }
-
-        public void put(String table, String key, String value) {
-            if (!mValidKeys.contains(key(table, key))) {
-                throw new IllegalArgumentException("Key " + table + " " + key
-                        + " not acquired for this overrider");
-            }
-            mValueMap.put(key(table, key), value);
-        }
-
-        public void remove(String table, String key) {
-            if (!mValidKeys.contains(key(table, key))) {
-                throw new IllegalArgumentException("Key " + table + " " + key
-                        + " not acquired for this overrider");
-            }
-            mValueMap.remove(key(table, key));
-        }
-
-        public String get(String table, String key) {
-            if (!mValidKeys.contains(key(table, key))) {
-                throw new IllegalArgumentException("Key " + table + " " + key
-                        + " not acquired for this overrider");
-            }
-            Log.d(TAG, "Get " + table + " " + key + " " + mValueMap.get(key(table, key)));
-            return mValueMap.get(key(table, key));
-        }
-
-        public static class Builder {
-            private final FakeSettingsProvider mProvider;
-            private final SysuiTestCase mOwner;
-            private Set<String> mKeys = new ArraySet<>();
-            private Map<String, String> mValues = new ArrayMap<>();
-
-            private Builder(FakeSettingsProvider provider, SysuiTestCase test) {
-                mProvider = provider;
-                mOwner = test;
-            }
-
-            public Builder addSetting(String table, String key) {
-                mKeys.add(key(table, key));
-                return this;
-            }
-
-            public Builder addSetting(String table, String key, String value) {
-                addSetting(table, key);
-                mValues.put(key(table, key), value);
-                return this;
-            }
-
-            public SettingOverrider build() throws AcquireTimeoutException {
-                SettingOverrider overrider = new SettingOverrider(mKeys, mProvider);
-                mProvider.acquireSettings(overrider, mKeys, mOwner);
-                mValues.forEach((key, value) -> overrider.putDirect(key, value));
-                return overrider;
-            }
-        }
-    }
-
-    public static class AcquireTimeoutException extends Exception {
-        public AcquireTimeoutException(String str) {
-            super(str);
-        }
-    }
-
-    private static String key(String table, String key) {
-        return table + "_" + key;
-    }
-
-    /**
-     * Since the settings provider is cached inside android.provider.Settings, this must
-     * be gotten statically to ensure there is only one instance referenced.
-     * @param settings
-     */
-    public static FakeSettingsProvider getFakeSettingsProvider(ContentProviderClient settings,
-            ContentResolver resolver) {
-        if (sInstance == null) {
-            sInstance = new FakeSettingsProvider(settings, resolver);
-        }
-        return sInstance;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
index b118fdc..d94ecc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
@@ -14,6 +14,9 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+import android.testing.LeakCheck.Tracker;
+
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.policy.CallbackController;
 
@@ -24,7 +27,7 @@
 
     private final Tracker mTracker;
 
-    public BaseLeakChecker(LeakCheckedTest test, String tag) {
+    public BaseLeakChecker(LeakCheck test, String tag) {
         mTracker = test.getTracker(tag);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index fa07d33..a843cca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -15,6 +15,7 @@
 package com.android.systemui.utils.leaks;
 
 import android.os.Bundle;
+import android.testing.LeakCheck;
 
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -24,7 +25,7 @@
 
 public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback>
         implements BatteryController {
-    public FakeBatteryController(LeakCheckedTest test) {
+    public FakeBatteryController(LeakCheck test) {
         super(test, "battery");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index 6074a01..0ba0319 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.BluetoothController.Callback;
@@ -23,7 +25,7 @@
 public class FakeBluetoothController extends BaseLeakChecker<Callback> implements
         BluetoothController {
 
-    public FakeBluetoothController(LeakCheckedTest test) {
+    public FakeBluetoothController(LeakCheck test) {
         super(test, "bluetooth");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
index 08211f8..51149ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
@@ -14,13 +14,15 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.Callback;
 
 import java.util.Set;
 
 public class FakeCastController extends BaseLeakChecker<Callback> implements CastController {
-    public FakeCastController(LeakCheckedTest test) {
+    public FakeCastController(LeakCheck test) {
         super(test, "cast");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
index 857a785..886722e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
@@ -14,12 +14,14 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
 
 public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController {
 
-    public FakeDataSaverController(LeakCheckedTest test) {
+    public FakeDataSaverController(LeakCheck test) {
         super(test, "datasaver");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
index c0f5783..b9d188a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
@@ -14,7 +14,8 @@
 
 package com.android.systemui.utils.leaks;
 
-import static org.mockito.Mockito.mock;
+import android.testing.LeakCheck;
+import android.testing.LeakCheck.Tracker;
 
 import com.android.systemui.statusbar.policy.ExtensionController;
 
@@ -25,7 +26,7 @@
 
     private final Tracker mTracker;
 
-    public FakeExtensionController(LeakCheckedTest test) {
+    public FakeExtensionController(LeakCheck test) {
         mTracker = test.getTracker("extension");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index 630abd7..f6fd2cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -14,12 +14,14 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
 
 public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener>
         implements FlashlightController {
-    public FakeFlashlightController(LeakCheckedTest test) {
+    public FakeFlashlightController(LeakCheck test) {
         super(test, "flashlight");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
index 781960d..69e2361 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
@@ -14,12 +14,14 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.HotspotController.Callback;
 
 public class FakeHotspotController extends BaseLeakChecker<Callback> implements HotspotController {
 
-    public FakeHotspotController(LeakCheckedTest test) {
+    public FakeHotspotController(LeakCheck test) {
         super(test, "hotspot");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
index 21871fc..51e35cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
@@ -14,13 +14,15 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 
 public class FakeKeyguardMonitor implements KeyguardMonitor {
 
     private final BaseLeakChecker<Callback> mCallbackController;
 
-    public FakeKeyguardMonitor(LeakCheckedTest test) {
+    public FakeKeyguardMonitor(LeakCheck test) {
         mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
index eab436c..29d7f1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
@@ -14,12 +14,14 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.LocationController.LocationSettingsChangeCallback;
 
 public class FakeLocationController extends BaseLeakChecker<LocationSettingsChangeCallback>
         implements LocationController {
-    public FakeLocationController(LeakCheckedTest test) {
+    public FakeLocationController(LeakCheck test) {
         super(test, "location");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
index 0ec0d77..18b07cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
@@ -14,12 +14,14 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
 
 public class FakeManagedProfileController extends BaseLeakChecker<Callback> implements
         ManagedProfileController {
-    public FakeManagedProfileController(LeakCheckedTest test) {
+    public FakeManagedProfileController(LeakCheck test) {
         super(test, "profile");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
index 47ed5ca..64fe8dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -15,25 +15,23 @@
 package com.android.systemui.utils.leaks;
 
 import android.os.Bundle;
+import android.testing.LeakCheck;
 
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
 public class FakeNetworkController extends BaseLeakChecker<SignalCallback>
         implements NetworkController {
 
     private final FakeDataSaverController mDataSaverController;
     private final BaseLeakChecker<EmergencyListener> mEmergencyChecker;
 
-    public FakeNetworkController(LeakCheckedTest test) {
+    public FakeNetworkController(LeakCheck test) {
         super(test, "network");
         mDataSaverController = new FakeDataSaverController(test);
-        mEmergencyChecker = new BaseLeakChecker<EmergencyListener>(test, "emergency");
+        mEmergencyChecker = new BaseLeakChecker<>(test, "emergency");
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
index 707fc4b..5ae8e22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
@@ -14,13 +14,15 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
 
 public class FakeNextAlarmController extends BaseLeakChecker<NextAlarmChangeCallback>
         implements NextAlarmController {
 
-    public FakeNextAlarmController(LeakCheckedTest test) {
+    public FakeNextAlarmController(LeakCheck test) {
         super(test, "alarm");
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
index 59a9361..0a83a89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -15,17 +15,17 @@
 package com.android.systemui.utils.leaks;
 
 import android.content.Context;
+import android.testing.LeakCheck;
 
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 
-public class FakePluginManager extends PluginManager {
+public class FakePluginManager implements PluginManager {
 
     private final BaseLeakChecker<PluginListener> mLeakChecker;
 
-    public FakePluginManager(Context context, LeakCheckedTest test) {
-        super(context);
+    public FakePluginManager(LeakCheck test) {
         mLeakChecker = new BaseLeakChecker<>(test, "Plugin");
     }
 
@@ -36,11 +36,38 @@
     }
 
     @Override
+    public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {
+        mLeakChecker.addCallback(listener);
+    }
+
+    @Override
+    public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
+            boolean allowMultiple) {
+        mLeakChecker.addCallback(listener);
+    }
+
+    @Override
+    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            Class<?> cls) {
+        mLeakChecker.addCallback(listener);
+    }
+
+    @Override
     public void removePluginListener(PluginListener<?> listener) {
         mLeakChecker.removeCallback(listener);
     }
 
     @Override
+    public <T> boolean dependsOn(Plugin p, Class<T> cls) {
+        return false;
+    }
+
+    @Override
+    public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
+        return null;
+    }
+
+    @Override
     public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
         return null;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index 00e2404..d60fe78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -14,12 +14,14 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
 
 public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback>
         implements RotationLockController {
-    public FakeRotationLockController(LeakCheckedTest test) {
+    public FakeRotationLockController(LeakCheck test) {
         super(test, "rotation");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
index 2d53c77..157b8a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -14,12 +14,14 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
 
 public class FakeSecurityController extends BaseLeakChecker<SecurityControllerCallback>
         implements SecurityController {
-    public FakeSecurityController(LeakCheckedTest test) {
+    public FakeSecurityController(LeakCheck test) {
         super(test, "security");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index b13535f..6b501af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
@@ -21,7 +23,7 @@
 public class FakeStatusBarIconController extends BaseLeakChecker<IconManager>
         implements StatusBarIconController {
 
-    public FakeStatusBarIconController(LeakCheckedTest test) {
+    public FakeStatusBarIconController(LeakCheck test) {
         super(test, "StatusBarGroup");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
index b841ce9..8db82e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
@@ -15,6 +15,7 @@
 package com.android.systemui.utils.leaks;
 
 import android.content.Context;
+import android.testing.LeakCheck;
 
 import com.android.systemui.tuner.TunerService;
 
@@ -22,10 +23,8 @@
 
     private final BaseLeakChecker<Tunable> mBaseLeakChecker;
 
-    public FakeTunerService(Context context, LeakCheckedTest test) {
-        super(context);
+    public FakeTunerService(LeakCheck test) {
         mBaseLeakChecker = new BaseLeakChecker<>(test, "tunable");
-        destroy();
     }
 
     @Override
@@ -40,4 +39,39 @@
     public void removeTunable(Tunable tunable) {
         mBaseLeakChecker.removeCallback(tunable);
     }
+
+    @Override
+    public void clearAll() {
+
+    }
+
+    @Override
+    public void destroy() {
+
+    }
+
+    @Override
+    public String getValue(String setting) {
+        return null;
+    }
+
+    @Override
+    public int getValue(String setting, int def) {
+        return def;
+    }
+
+    @Override
+    public String getValue(String setting, String def) {
+        return def;
+    }
+
+    @Override
+    public void setValue(String setting, String value) {
+
+    }
+
+    @Override
+    public void setValue(String setting, int value) {
+
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
index 578b310..f7ef653a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
@@ -14,12 +14,14 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.testing.LeakCheck;
+
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 
 public class FakeUserInfoController extends BaseLeakChecker<OnUserInfoChangedListener>
         implements UserInfoController {
-    public FakeUserInfoController(LeakCheckedTest test) {
+    public FakeUserInfoController(LeakCheck test) {
         super(test, "user_info");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
index 7581363..fb9bf7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
@@ -18,12 +18,13 @@
 import android.net.Uri;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ZenRule;
+import android.testing.LeakCheck;
 
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.ZenModeController.Callback;
 
 public class FakeZenModeController extends BaseLeakChecker<Callback> implements ZenModeController {
-    public FakeZenModeController(LeakCheckedTest test) {
+    public FakeZenModeController(LeakCheck test) {
         super(test, "zen");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index 6c51524..94af7733 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -15,8 +15,8 @@
 package com.android.systemui.utils.leaks;
 
 import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
 
+import android.testing.LeakCheck;
 import android.util.ArrayMap;
 
 import com.android.systemui.SysuiTestCase;
@@ -25,7 +25,6 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.HotspotController;
@@ -41,12 +40,7 @@
 
 import org.junit.Assert;
 import org.junit.Rule;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -56,10 +50,7 @@
 public abstract class LeakCheckedTest extends SysuiTestCase {
     private static final String TAG = "LeakCheckedTest";
 
-    private final Map<String, Tracker> mTrackers = new HashMap<>();
-    private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
-
-    public static final Class<?>[] ALL_SUPPORTED_CLASSES = new Class[] {
+    public static final Class<?>[] ALL_SUPPORTED_CLASSES = new Class[]{
             BluetoothController.class,
             LocationController.class,
             RotationLockController.class,
@@ -80,71 +71,11 @@
     };
 
     @Rule
-    public TestWatcher successWatcher = new TestWatcher() {
-        @Override
-        protected void succeeded(Description description) {
-            verify();
-        }
-    };
-
-    public <T> T getLeakChecker(Class<T> cls) {
-        Object obj = mLeakCheckers.get(cls);
-        if (obj == null) {
-            // Lazy create checkers so we only have the ones we need.
-            if (cls == BluetoothController.class) {
-                obj = new FakeBluetoothController(this);
-            } else if (cls == LocationController.class) {
-                obj = new FakeLocationController(this);
-            } else if (cls == RotationLockController.class) {
-                obj = new FakeRotationLockController(this);
-            } else if (cls == ZenModeController.class) {
-                obj = new FakeZenModeController(this);
-            } else if (cls == CastController.class) {
-                obj = new FakeCastController(this);
-            } else if (cls == HotspotController.class) {
-                obj = new FakeHotspotController(this);
-            } else if (cls == FlashlightController.class) {
-                obj = new FakeFlashlightController(this);
-            } else if (cls == UserInfoController.class) {
-                obj = new FakeUserInfoController(this);
-            } else if (cls == KeyguardMonitor.class) {
-                obj = new FakeKeyguardMonitor(this);
-            } else if (cls == BatteryController.class) {
-                obj = new FakeBatteryController(this);
-            } else if (cls == SecurityController.class) {
-                obj = new FakeSecurityController(this);
-            } else if (cls == ManagedProfileController.class) {
-                obj = new FakeManagedProfileController(this);
-            } else if (cls == NextAlarmController.class) {
-                obj = new FakeNextAlarmController(this);
-            } else if (cls == NetworkController.class) {
-                obj = new FakeNetworkController(this);
-            } else if (cls == PluginManager.class) {
-                obj = new FakePluginManager(mContext, this);
-            } else if (cls == TunerService.class) {
-                obj = new FakeTunerService(mContext, this);
-            } else if (cls == StatusBarIconController.class) {
-                obj = new FakeStatusBarIconController(this);
-            } else {
-                Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
-            }
-            mLeakCheckers.put(cls, obj);
-        }
-        return (T) obj;
-    }
+    public SysuiLeakCheck mLeakCheck = new SysuiLeakCheck();
 
     @Override
-    public Tracker getTracker(String tag) {
-        Tracker t = mTrackers.get(tag);
-        if (t == null) {
-            t = new Tracker();
-            mTrackers.put(tag, t);
-        }
-        return t;
-    }
-
-    public void verify() {
-        mTrackers.values().forEach(Tracker::verify);
+    public LeakCheck getLeakCheck() {
+        return mLeakCheck;
     }
 
     public void injectLeakCheckedDependencies(Class<?>... cls) {
@@ -154,26 +85,61 @@
     }
 
     public <T> void injectLeakCheckedDependency(Class<T> c) {
-        injectTestDependency(c, getLeakChecker(c));
+        mDependency.injectTestDependency(c, mLeakCheck.getLeakChecker(c));
     }
 
-    public <T extends CallbackController> T addListening(T mock, Class<T> cls, String tag) {
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker(tag).getLeakInfo(invocation.getArguments()[0])
-                        .addAllocation(new Throwable());
-                return null;
+    public static class SysuiLeakCheck extends LeakCheck {
+
+        private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
+
+        public SysuiLeakCheck() {
+            super();
+        }
+
+        public <T> T getLeakChecker(Class<T> cls) {
+            Object obj = mLeakCheckers.get(cls);
+            if (obj == null) {
+                // Lazy create checkers so we only have the ones we need.
+                if (cls == BluetoothController.class) {
+                    obj = new FakeBluetoothController(this);
+                } else if (cls == LocationController.class) {
+                    obj = new FakeLocationController(this);
+                } else if (cls == RotationLockController.class) {
+                    obj = new FakeRotationLockController(this);
+                } else if (cls == ZenModeController.class) {
+                    obj = new FakeZenModeController(this);
+                } else if (cls == CastController.class) {
+                    obj = new FakeCastController(this);
+                } else if (cls == HotspotController.class) {
+                    obj = new FakeHotspotController(this);
+                } else if (cls == FlashlightController.class) {
+                    obj = new FakeFlashlightController(this);
+                } else if (cls == UserInfoController.class) {
+                    obj = new FakeUserInfoController(this);
+                } else if (cls == KeyguardMonitor.class) {
+                    obj = new FakeKeyguardMonitor(this);
+                } else if (cls == BatteryController.class) {
+                    obj = new FakeBatteryController(this);
+                } else if (cls == SecurityController.class) {
+                    obj = new FakeSecurityController(this);
+                } else if (cls == ManagedProfileController.class) {
+                    obj = new FakeManagedProfileController(this);
+                } else if (cls == NextAlarmController.class) {
+                    obj = new FakeNextAlarmController(this);
+                } else if (cls == NetworkController.class) {
+                    obj = new FakeNetworkController(this);
+                } else if (cls == PluginManager.class) {
+                    obj = new FakePluginManager(this);
+                } else if (cls == TunerService.class) {
+                    obj = new FakeTunerService(this);
+                } else if (cls == StatusBarIconController.class) {
+                    obj = new FakeStatusBarIconController(this);
+                } else {
+                    Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
+                }
+                mLeakCheckers.put(cls, obj);
             }
-        }).when(mock).addCallback(any());
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations();
-                return null;
-            }
-        }).when(mock).removeCallback(any());
-        mLeakCheckers.put(cls, mock);
-        return mock;
+            return (T) obj;
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java
deleted file mode 100644
index 1d016fb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.utils.leaks;
-
-import android.util.Log;
-
-import org.junit.Assert;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-public class LeakInfo {
-    private static final String TAG = "LeakInfo";
-    private List<Throwable> mThrowables = new ArrayList<>();
-
-    LeakInfo() {
-    }
-
-    public void addAllocation(Throwable t) {
-        // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
-        mThrowables.add(t);
-    }
-
-    public void clearAllocations() {
-        mThrowables.clear();
-    }
-
-    void verify() {
-        if (mThrowables.size() == 0) return;
-        Log.e(TAG, "Listener or binding not properly released");
-        for (Throwable t : mThrowables) {
-            Log.e(TAG, "Allocation found", t);
-        }
-        StringWriter writer = new StringWriter();
-        mThrowables.get(0).printStackTrace(new PrintWriter(writer));
-        Assert.fail("Listener or binding not properly released\n"
-                + writer.toString());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java
deleted file mode 100644
index 26ffd10..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.utils.leaks;
-
-import android.util.ArrayMap;
-
-import com.android.systemui.utils.leaks.LeakInfo;
-
-import java.util.Map;
-
-public class Tracker {
-    private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
-
-    public LeakInfo getLeakInfo(Object object) {
-        LeakInfo leakInfo = mObjects.get(object);
-        if (leakInfo == null) {
-            leakInfo = new LeakInfo();
-            mObjects.put(object, leakInfo);
-        }
-        return leakInfo;
-    }
-
-    void verify() {
-        mObjects.values().forEach(LeakInfo::verify);
-    }
-}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 25481ce..03582ea 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3601,6 +3601,9 @@
     // FIELD: Settings inline search result value
     FIELD_SETTINGS_SEARCH_INLINE_RESULT_VALUE = 880;
 
+    // ACTION: Settings > Search > Click saved queries
+    ACTION_CLICK_SETTINGS_SEARCH_SAVED_QUERY = 881;
+
     // ---- 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 b56035f..98ce00e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -416,10 +416,12 @@
                     removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                     // We will update when the automation service dies.
-                    UserState userState = getCurrentUserStateLocked();
-                    if (!userState.isUiAutomationSuppressingOtherServices()) {
-                        if (readConfigurationForUserStateLocked(userState)) {
-                            onUserStateChangedLocked(userState);
+                    synchronized (mLock) {
+                        UserState userState = getCurrentUserStateLocked();
+                        if (!userState.isUiAutomationSuppressingOtherServices()) {
+                            if (readConfigurationForUserStateLocked(userState)) {
+                                onUserStateChangedLocked(userState);
+                            }
                         }
                     }
                 } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index b6c60d0..295244f 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -60,6 +60,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.LocalLog;
+import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.view.autofill.AutofillId;
@@ -73,6 +74,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -802,35 +804,100 @@
                 Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
             }
 
-            if (saveInfo == null || saveInfo.getSavableIds() == null
-                    || saveInfo.getSavableIds().isEmpty()) {
+            /*
+             * 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;
             }
 
-            final int size = saveInfo.getSavableIds().size();
-            for (int i = 0; i < size; i++) {
-                final AutofillId id = saveInfo.getSavableIds().valueAt(i);
+            final AutofillId[] requiredIds = saveInfo.getRequiredIds();
+            if (requiredIds == null || requiredIds.length == 0) {
+                Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo");
+                return;
+            }
+
+            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.mValueUpdated) {
+                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 == null || state.mAutofillValue.equals(filledValue)) {
-                        continue;
+                    if (!state.mAutofillValue.equals(filledValue)) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
+                                    + filledValue + " => " + state.mAutofillValue);
+                        }
+                        atLeastOneChanged = true;
                     }
-                    if (DEBUG) {
-                        Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
-                                + state.mAutofillValue);
+                } else {
+                    if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "finishSessionLocked(): empty value for " + id );
+                        }
+                        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);
                     return;
                 }
             }
-
             // Nothing changed...
             if (DEBUG) {
-                Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities");
+                Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
+                        + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
+                        + ", atLeastOneChanged=" + atLeastOneChanged);
             }
+            removeSelf();
         }
 
         /**
@@ -954,9 +1021,10 @@
             String filterText = "";
             if (value != null) {
                 // TODO(b/33197203): Handle other AutofillValue types
-                final CharSequence text = value.getTextValue();
-                if (text != null) {
-                    filterText = text.toString();
+                if (value.isText()) {
+                    filterText = value.getTextValue().toString();
+                } else {
+                    Log.w(TAG, value + " could not be autofilled into " + this);
                 }
             }
 
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index eeff37c..47251db 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -240,7 +240,7 @@
         }
         mBinding = false;
         if (isBound()) {
-            // TODO(b/33197203, b/35395043): synchronize access instead
+            // TODO(b/33197203): synchronize access instead?
             // Need to double check if it's null, since it could be set on onServiceDisconnected()
             if (mAutoFillService != null) {
                 try {
@@ -322,7 +322,7 @@
             }
 
             try {
-                // TODO(b/33197203, b/35395043): synchronize access instead
+                // TODO(b/33197203): synchronize access instead?
                 // Need to double check if it's null, since it could be set on
                 // onServiceDisconnected()
                 if (mAutoFillService != null) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 0d5a3e0..4670eed 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4003,12 +4003,12 @@
     public Account[] getAccountsAsUser(String type, int userId, String opPackageName) {
         int callingUid = Binder.getCallingUid();
         mAppOpsManager.checkPackage(callingUid, opPackageName);
-        return getAccountsAsUser(type, userId, opPackageName /* callingPackage */, -1,
+        return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1,
                 opPackageName, false /* includeUserManagedNotVisible */);
     }
 
     @NonNull
-    private Account[] getAccountsAsUser(
+    private Account[] getAccountsAsUserForPackage(
             String type,
             int userId,
             String callingPackage,
@@ -4061,7 +4061,7 @@
             return getAccountsInternal(
                     accounts,
                     callingUid,
-                    callingPackage,
+                    opPackageName,
                     visibleAccountTypes,
                     includeUserManagedNotVisible);
         } finally {
@@ -4178,7 +4178,7 @@
             throw new SecurityException("getAccountsForPackage() called from unauthorized uid "
                     + callingUid + " with uid=" + uid);
         }
-        return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid,
+        return getAccountsAsUserForPackage(null, UserHandle.getCallingUserId(), packageName, uid,
                 opPackageName, true /* includeUserManagedNotVisible */);
     }
 
@@ -4197,11 +4197,10 @@
             return EMPTY_ACCOUNT_ARRAY;
         }
         if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
-                && !isAccountManagedByCaller(type, callingUid, userId)) {
-            return EMPTY_ACCOUNT_ARRAY;
+                && (type != null && !isAccountManagedByCaller(type, callingUid, userId))) {
+                return EMPTY_ACCOUNT_ARRAY;
         }
-
-        return getAccountsAsUser(type, userId,
+        return getAccountsAsUserForPackage(type, userId,
                 packageName, packageUid, opPackageName, true /* includeUserManagedNotVisible */);
     }
 
@@ -5371,10 +5370,13 @@
     @NonNull
     private Account[] filterAccounts(UserAccounts accounts, Account[] unfiltered, int callingUid,
             String callingPackage, boolean includeManagedNotVisible) {
-        // filter based on visibility.
+        String visibilityFilterPackage = callingPackage;
+        if (visibilityFilterPackage == null) {
+            visibilityFilterPackage = getPackageNameForUid(callingUid);
+        }
         Map<Account, Integer> firstPass = new LinkedHashMap<>();
         for (Account account : unfiltered) {
-            int visibility = resolveAccountVisibility(account, callingPackage, accounts);
+            int visibility = resolveAccountVisibility(account, visibilityFilterPackage, accounts);
             if ((visibility == AccountManager.VISIBILITY_VISIBLE
                     || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE)
                     || (includeManagedNotVisible
@@ -5394,7 +5396,7 @@
     @NonNull
     private Map<Account, Integer> filterSharedAccounts(UserAccounts userAccounts,
             @NonNull Map<Account, Integer> unfiltered, int callingUid,
-            String callingPackage) {
+            @Nullable String callingPackage) {
         // first part is to filter shared accounts.
         // unfiltered type check is not necessary.
         if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0
@@ -5474,7 +5476,7 @@
      */
     @NonNull
     protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
-            int callingUid, String callingPackage, boolean includeManagedNotVisible) {
+            int callingUid, @Nullable String callingPackage, boolean includeManagedNotVisible) {
         if (callingPackage == null) {
             callingPackage = getPackageNameForUid(callingUid);
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6bf77ae..55d661c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6691,16 +6691,15 @@
                     : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop,
                                        profileStreamingOutput);
 
-            // We deprecated Build.SERIAL and only apps that target pre NMR1
-            // SDK can see it. Since access to the serial is now behind a
-            // permission we push down the value.
+            // We deprecated Build.SERIAL and it is not accessible to
+            // apps that target the v2 security sandbox. Since access to
+            // the serial is now behind a permission we push down the value.
             String buildSerial = Build.UNKNOWN;
-            // TODO: SHTOPSHIP Uncomment the check when clients migrate
-//            if (appInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+            if (appInfo.targetSandboxVersion != 2) {
                 buildSerial = IDeviceIdentifiersPolicyService.Stub.asInterface(
                         ServiceManager.getService(Context.DEVICE_IDENTIFIERS_SERVICE))
                         .getSerial();
-//            }
+            }
 
             // Check if this is a secondary process that should be incorporated into some
             // currently active instrumentation.  (Note we do this AFTER all of the profiling
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 79b99a3..333d27b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1241,6 +1241,13 @@
     /** @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))) {
+            Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without"
+                    + "BIND_ACCESSIBILITY_SERVICE / callingPackage=" + callingPackage);
+            return;
+        }
         adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
                 Binder.getCallingUid());
     }
@@ -1552,6 +1559,13 @@
 
     /** @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))) {
+            Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
+                    + " BIND_ACCESSIBILITY_SERVICE  callingPackage=" + callingPackage);
+            return;
+        }
         setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
                 Binder.getCallingUid());
     }
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index d5fa26c..81e891a 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -151,7 +151,8 @@
     }
 
     private static void setDnsEvent(IpConnectivityEvent out, DnsEvent in) {
-        IpConnectivityLogClass.DNSLookupBatch dnsLookupBatch = new IpConnectivityLogClass.DNSLookupBatch();
+        IpConnectivityLogClass.DNSLookupBatch dnsLookupBatch =
+                new IpConnectivityLogClass.DNSLookupBatch();
         dnsLookupBatch.networkId = netIdOf(in.netId);
         dnsLookupBatch.eventTypes = bytesToInts(in.eventTypes);
         dnsLookupBatch.returnCodes = bytesToInts(in.returnCodes);
@@ -160,7 +161,8 @@
     }
 
     private static void setIpManagerEvent(IpConnectivityEvent out, IpManagerEvent in) {
-        IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent = new IpConnectivityLogClass.IpProvisioningEvent();
+        IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent =
+                new IpConnectivityLogClass.IpProvisioningEvent();
         ipProvisioningEvent.ifName = in.ifName;
         ipProvisioningEvent.eventType = in.eventType;
         ipProvisioningEvent.latencyMs = (int) in.durationMs;
@@ -168,14 +170,16 @@
     }
 
     private static void setIpReachabilityEvent(IpConnectivityEvent out, IpReachabilityEvent in) {
-        IpConnectivityLogClass.IpReachabilityEvent ipReachabilityEvent = new IpConnectivityLogClass.IpReachabilityEvent();
+        IpConnectivityLogClass.IpReachabilityEvent ipReachabilityEvent =
+                new IpConnectivityLogClass.IpReachabilityEvent();
         ipReachabilityEvent.ifName = in.ifName;
         ipReachabilityEvent.eventType = in.eventType;
         out.setIpReachabilityEvent(ipReachabilityEvent);
     }
 
     private static void setDefaultNetworkEvent(IpConnectivityEvent out, DefaultNetworkEvent in) {
-        IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent = new IpConnectivityLogClass.DefaultNetworkEvent();
+        IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent =
+                new IpConnectivityLogClass.DefaultNetworkEvent();
         defaultNetworkEvent.networkId = netIdOf(in.netId);
         defaultNetworkEvent.previousNetworkId = netIdOf(in.prevNetId);
         defaultNetworkEvent.transportTypes = in.transportTypes;
@@ -184,7 +188,8 @@
     }
 
     private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) {
-        IpConnectivityLogClass.NetworkEvent networkEvent = new IpConnectivityLogClass.NetworkEvent();
+        IpConnectivityLogClass.NetworkEvent networkEvent =
+                new IpConnectivityLogClass.NetworkEvent();
         networkEvent.networkId = netIdOf(in.netId);
         networkEvent.eventType = in.eventType;
         networkEvent.latencyMs = (int) in.durationMs;
@@ -192,7 +197,8 @@
     }
 
     private static void setValidationProbeEvent(IpConnectivityEvent out, ValidationProbeEvent in) {
-        IpConnectivityLogClass.ValidationProbeEvent validationProbeEvent = new IpConnectivityLogClass.ValidationProbeEvent();
+        IpConnectivityLogClass.ValidationProbeEvent validationProbeEvent =
+                new IpConnectivityLogClass.ValidationProbeEvent();
         validationProbeEvent.networkId = netIdOf(in.netId);
         validationProbeEvent.latencyMs = (int) in.durationMs;
         validationProbeEvent.probeType = in.probeType;
@@ -201,8 +207,10 @@
     }
 
     private static void setApfProgramEvent(IpConnectivityEvent out, ApfProgramEvent in) {
-        IpConnectivityLogClass.ApfProgramEvent apfProgramEvent = new IpConnectivityLogClass.ApfProgramEvent();
+        IpConnectivityLogClass.ApfProgramEvent apfProgramEvent =
+                new IpConnectivityLogClass.ApfProgramEvent();
         apfProgramEvent.lifetime = in.lifetime;
+        apfProgramEvent.effectiveLifetime = in.actualLifetime;
         apfProgramEvent.filteredRas = in.filteredRas;
         apfProgramEvent.currentRas = in.currentRas;
         apfProgramEvent.programLength = in.programLength;
@@ -216,7 +224,8 @@
     }
 
     private static void setApfStats(IpConnectivityEvent out, ApfStats in) {
-        IpConnectivityLogClass.ApfStatistics apfStatistics = new IpConnectivityLogClass.ApfStatistics();
+        IpConnectivityLogClass.ApfStatistics apfStatistics =
+                new IpConnectivityLogClass.ApfStatistics();
         apfStatistics.durationMs = in.durationMs;
         apfStatistics.receivedRas = in.receivedRas;
         apfStatistics.matchingRas = in.matchingRas;
@@ -224,6 +233,8 @@
         apfStatistics.zeroLifetimeRas = in.zeroLifetimeRas;
         apfStatistics.parseErrors = in.parseErrors;
         apfStatistics.programUpdates = in.programUpdates;
+        apfStatistics.programUpdatesAll = in.programUpdatesAll;
+        apfStatistics.programUpdatesAllowingMulticast = in.programUpdatesAllowingMulticast;
         apfStatistics.maxProgramSize = in.maxProgramSize;
         out.setApfStatistics(apfStatistics);
     }
diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
index 14f2e86..552f0d1 100644
--- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
+++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
@@ -83,6 +83,7 @@
             if (inLockoutMode) {
                 try {
                     Slog.w(TAG, "Forcing lockout (fp driver code should do this!)");
+                    stop(false); // cancel fingerprint authentication
                     receiver.onError(getHalDeviceId(),
                             FingerprintManager.FINGERPRINT_ERROR_LOCKOUT, 0 /* vendorCode */);
                 } catch (RemoteException e) {
@@ -107,7 +108,7 @@
     public int start() {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "start authentication: no fingeprintd!");
+            Slog.w(TAG, "start authentication: no fingerprint HAL!");
             return ERROR_ESRCH;
         }
         try {
@@ -130,7 +131,7 @@
     public int stop(boolean initiatedByClient) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "stopAuthentication: no fingeprintd!");
+            Slog.w(TAG, "stopAuthentication: no fingerprint HAL!");
             return ERROR_ESRCH;
         }
         try {
diff --git a/services/core/java/com/android/server/fingerprint/ClientMonitor.java b/services/core/java/com/android/server/fingerprint/ClientMonitor.java
index 43bb21d..492cd61 100644
--- a/services/core/java/com/android/server/fingerprint/ClientMonitor.java
+++ b/services/core/java/com/android/server/fingerprint/ClientMonitor.java
@@ -28,13 +28,13 @@
 import java.util.NoSuchElementException;
 
 /**
- * Abstract base class for keeping track and dispatching events from fingerprintd to the
+ * Abstract base class for keeping track and dispatching events from fingerprint HAL to the
  * the current client.  Subclasses are responsible for coordinating the interaction with
- * fingerprintd for the specific action (e.g. authenticate, enroll, enumerate, etc.).
+ * fingerprint HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
  */
 public abstract class ClientMonitor implements IBinder.DeathRecipient {
     protected static final String TAG = FingerprintService.TAG; // TODO: get specific name
-    protected static final int ERROR_ESRCH = 3; // Likely fingerprintd is dead. See errno.h.
+    protected static final int ERROR_ESRCH = 3; // Likely fingerprint HAL is dead. See errno.h.
     protected static final boolean DEBUG = FingerprintService.DEBUG;
     private IBinder mToken;
     private IFingerprintServiceReceiver mReceiver;
@@ -77,13 +77,13 @@
     }
 
     /**
-     * Contacts fingerprintd to start the client.
+     * Contacts fingerprint HAL to start the client.
      * @return 0 on succes, errno from driver on failure
      */
     public abstract int start();
 
     /**
-     * Contacts fingerprintd to stop the client.
+     * Contacts fingerprint HAL to stop the client.
      * @param initiatedByClient whether the operation is at the request of a client
      */
     public abstract int stop(boolean initiatedByClient);
@@ -108,7 +108,7 @@
     public abstract boolean onEnumerationResult(int fingerId, int groupId, int remaining);
 
     /**
-     * Called when we get notification from fingerprintd that an image has been acquired.
+     * Called when we get notification from fingerprint HAL that an image has been acquired.
      * Common to authenticate and enroll.
      * @param acquiredInfo info about the current image acquisition
      * @return true if client should be removed
@@ -131,7 +131,7 @@
     }
 
     /**
-     * Called when we get notification from fingerprintd that an error has occurred with the
+     * Called when we get notification from fingerprint HAL that an error has occurred with the
      * current operation. Common to authenticate, enroll, enumerate and remove.
      * @param error
      * @return true if client should be removed
diff --git a/services/core/java/com/android/server/fingerprint/EnrollClient.java b/services/core/java/com/android/server/fingerprint/EnrollClient.java
index eddcd5b..e1b78a8 100644
--- a/services/core/java/com/android/server/fingerprint/EnrollClient.java
+++ b/services/core/java/com/android/server/fingerprint/EnrollClient.java
@@ -80,7 +80,7 @@
     public int start() {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "enroll: no fingeprintd!");
+            Slog.w(TAG, "enroll: no fingerprint HAL!");
             return ERROR_ESRCH;
         }
         final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
@@ -102,7 +102,7 @@
     public int stop(boolean initiatedByClient) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "stopEnrollment: no fingeprintd!");
+            Slog.w(TAG, "stopEnrollment: no fingerprint HAL!");
             return ERROR_ESRCH;
         }
         try {
diff --git a/services/core/java/com/android/server/fingerprint/EnumerateClient.java b/services/core/java/com/android/server/fingerprint/EnumerateClient.java
index 55bf689..34f245f 100644
--- a/services/core/java/com/android/server/fingerprint/EnumerateClient.java
+++ b/services/core/java/com/android/server/fingerprint/EnumerateClient.java
@@ -58,7 +58,7 @@
     public int stop(boolean initiatedByClient) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "stopAuthentication: no fingeprintd!");
+            Slog.w(TAG, "stopAuthentication: no fingerprint HAL!");
             return ERROR_ESRCH;
         }
         try {
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 0f29942..3262151 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -206,7 +206,7 @@
 
     @Override
     public void serviceDied(long cookie) {
-        Slog.v(TAG, "fingerprintd died");
+        Slog.v(TAG, "fingerprint HAL died");
         MetricsLogger.count(mContext, "fingerprintd_died", 1);
         synchronized (this) {
             mDaemon = null;
@@ -235,7 +235,7 @@
             try {
                 mHalDeviceId = mDaemon.setNotify(mDaemonCallback);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to open fingeprintd HAL", e);
+                Slog.e(TAG, "Failed to open fingerprint HAL", e);
                 mDaemon = null; // try again later!
             }
 
@@ -391,7 +391,7 @@
     public long startPreEnroll(IBinder token) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "startPreEnroll: no fingeprintd!");
+            Slog.w(TAG, "startPreEnroll: no fingerprint HAL!");
             return 0;
         }
         try {
@@ -405,7 +405,7 @@
     public int startPostEnroll(IBinder token) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "startPostEnroll: no fingeprintd!");
+            Slog.w(TAG, "startPostEnroll: no fingerprint HAL!");
             return 0;
         }
         try {
@@ -417,7 +417,7 @@
     }
 
     /**
-     * Calls fingerprintd to switch states to the new task. If there's already a current task,
+     * Calls fingerprint HAL to switch states to the new task. If there's already a current task,
      * it calls cancel() and sets mPendingClient to begin when the current task finishes
      * ({@link FingerprintManager#FINGERPRINT_ERROR_CANCELED}).
      * @param newClient the new client that wants to connect
@@ -447,7 +447,7 @@
             IFingerprintServiceReceiver receiver, boolean restricted) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "startRemove: no fingeprintd!");
+            Slog.w(TAG, "startRemove: no fingerprint HAL!");
             return;
         }
         RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
@@ -469,7 +469,7 @@
         IFingerprintServiceReceiver receiver, boolean restricted) {
         IBiometricsFingerprint daemon = getFingerprintDaemon();
         if (daemon == null) {
-            Slog.w(TAG, "startEnumerate: no fingeprintd!");
+            Slog.w(TAG, "startEnumerate: no fingerprint HAL!");
             return;
         }
         EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token,
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index b085179..6af1c3b 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -288,6 +288,10 @@
         if (overlayPackage == null) {
             return false;
         }
+        // Static overlay is always being enabled.
+        if (!enable && overlayPackage.isStaticOverlay) {
+            return false;
+        }
 
         try {
             final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
@@ -333,17 +337,28 @@
         }
     }
 
+    boolean isPackageUpdatableOverlay(@NonNull final String packageName, final int userId) {
+        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
+        if (overlayPackage == null || overlayPackage.isStaticOverlay) {
+            return false;
+        }
+        return true;
+    }
+
     boolean setPriority(@NonNull final String packageName,
             @NonNull final String newParentPackageName, final int userId) {
-        return mSettings.setPriority(packageName, newParentPackageName, userId);
+        return isPackageUpdatableOverlay(packageName, userId) &&
+                mSettings.setPriority(packageName, newParentPackageName, userId);
     }
 
     boolean setHighestPriority(@NonNull final String packageName, final int userId) {
-        return mSettings.setHighestPriority(packageName, userId);
+        return isPackageUpdatableOverlay(packageName, userId) &&
+                mSettings.setHighestPriority(packageName, userId);
     }
 
     boolean setLowestPriority(@NonNull final String packageName, final int userId) {
-        return mSettings.setLowestPriority(packageName, userId);
+        return isPackageUpdatableOverlay(packageName, userId) &&
+                mSettings.setLowestPriority(packageName, userId);
     }
 
     void onDump(@NonNull final PrintWriter pw) {
@@ -368,7 +383,9 @@
     private void updateState(@Nullable final PackageInfo targetPackage,
             @NonNull final PackageInfo overlayPackage, final int userId)
         throws OverlayManagerSettings.BadKeyException {
-        if (targetPackage != null) {
+        // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
+        if (targetPackage != null &&
+                !("android".equals(targetPackage.packageName) && overlayPackage.isStaticOverlay)) {
             mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c27806d..e348051 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -399,7 +399,7 @@
     private static final boolean HIDE_EPHEMERAL_APIS = false;
 
     private static final boolean ENABLE_FREE_CACHE_V2 =
-            SystemProperties.getBoolean("fw.free_cache_v2", false);
+            SystemProperties.getBoolean("fw.free_cache_v2", true);
 
     private static final int RADIO_UID = Process.PHONE_UID;
     private static final int LOG_UID = Process.LOG_UID;
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 7885748..21fe5ba 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -720,7 +720,11 @@
 
                 // Disable dynamic shortcuts whose target activity is gone.
                 if (si.isDynamic()) {
-                    if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) {
+                    if (si.getActivity() == null) {
+                        // Note if it's dynamic, it must have a target activity, but b/36228253.
+                        s.wtf("null activity detected.");
+                        // TODO Maybe remove it?
+                    } else if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) {
                         Slog.w(TAG, String.format(
                                 "%s is no longer main activity. Disabling shorcut %s.",
                                 getPackageName(), si.getId()));
@@ -931,6 +935,10 @@
             }
 
             final ComponentName activity = si.getActivity();
+            if (activity == null) {
+                mShortcutUser.mService.wtf("null activity detected.");
+                continue;
+            }
 
             ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
             if (list == null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 8998212..ef46bae 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3237,6 +3237,10 @@
     boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) {
         final long start = injectElapsedRealtime();
         try {
+            if (activity == null) {
+                wtf("null activity detected");
+                return false;
+            }
             if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 4191d31..75a7385 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -692,7 +692,7 @@
     private void getDefaultNextAppTransitionStartRect(Rect rect) {
         if (mDefaultNextAppTransitionAnimationSpec == null ||
                 mDefaultNextAppTransitionAnimationSpec.rect == null) {
-            Slog.wtf(TAG, "Starting rect for app requested, but none available", new Throwable());
+            Slog.e(TAG, "Starting rect for app requested, but none available", new Throwable());
             rect.setEmpty();
         } else {
             rect.set(mDefaultNextAppTransitionAnimationSpec.rect);
@@ -705,7 +705,7 @@
             spec = mDefaultNextAppTransitionAnimationSpec;
         }
         if (spec == null || spec.rect == null) {
-            Slog.wtf(TAG, "Starting rect for task: " + taskId + " requested, but not available",
+            Slog.e(TAG, "Starting rect for task: " + taskId + " requested, but not available",
                     new Throwable());
             rect.setEmpty();
         } else {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cb3a663..e5b00f3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -73,18 +73,24 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION;
 import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
 import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER;
 import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
+import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
+import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
+import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
+import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
 import static com.android.server.wm.WindowManagerService.dipToPixel;
 import static com.android.server.wm.WindowManagerService.logSurface;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
@@ -94,6 +100,7 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager.StackId;
+import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
@@ -113,6 +120,7 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.InputDevice;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManagerPolicy;
@@ -181,12 +189,27 @@
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
     private final Display mDisplay;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    /**
+     * For default display it contains real metrics, empty for others.
+     * @see WindowManagerService#createWatermarkInTransaction()
+     */
+    final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
+    /** @see #computeCompatSmallestWidth(boolean, int, int, int, int) */
+    private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
+    /**
+     * Compat metrics computed based on {@link #mDisplayMetrics}.
+     * @see #updateDisplayAndOrientation(int)
+     */
+    private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
+
+    /** The desired scaling factor for compatible apps. */
+    float mCompatibleScreenScale;
 
     /**
      * Current rotation of the display.
      * Constants as per {@link android.view.Surface.Rotation}.
      *
-     * @see WindowManagerService#updateRotationUncheckedLocked(boolean, int)
+     * @see #updateRotationUnchecked(boolean)
      */
     private int mRotation = 0;
     /**
@@ -200,7 +223,7 @@
      * Flag indicating that the application is receiving an orientation that has different metrics
      * than it expected. E.g. Portrait instead of Landscape.
      *
-     * @see WindowManagerService#updateRotationUncheckedLocked(boolean, int)
+     * @see #updateRotationUnchecked(boolean)
      */
     private boolean mAltOrientation = false;
     /**
@@ -218,7 +241,7 @@
      */
     private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
 
-    Rect mBaseDisplayRect = new Rect();
+    private Rect mBaseDisplayRect = new Rect();
     private Rect mContentRect = new Rect();
 
     // Accessed directly by all users.
@@ -828,6 +851,514 @@
         return mLastWindowForcedOrientation;
     }
 
+    /**
+     * Update rotation of the display.
+     *
+     * Returns true if the rotation has been changed.  In this case YOU MUST CALL
+     * {@link WindowManagerService#sendNewConfiguration(int)} TO UNFREEZE THE SCREEN.
+     */
+    boolean updateRotationUnchecked(boolean inTransaction) {
+        if (mService.mDeferredRotationPauseCount > 0) {
+            // Rotation updates have been paused temporarily.  Defer the update until
+            // updates have been resumed.
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");
+            return false;
+        }
+
+        ScreenRotationAnimation screenRotationAnimation =
+                mService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
+        if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+            // Rotation updates cannot be performed while the previous rotation change
+            // animation is still in progress.  Skip this update.  We will try updating
+            // again after the animation is finished and the display is unfrozen.
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress.");
+            return false;
+        }
+        if (mService.mDisplayFrozen) {
+            // Even if the screen rotation animation has finished (e.g. isAnimating
+            // returns false), there is still some time where we haven't yet unfrozen
+            // the display. We also need to abort rotation here.
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+                    "Deferring rotation, still finishing previous rotation");
+            return false;
+        }
+
+        if (!mService.mDisplayEnabled) {
+            // No point choosing a rotation if the display is not enabled.
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled.");
+            return false;
+        }
+
+        final int oldRotation = mRotation;
+        final int lastOrientation = mLastOrientation;
+        final boolean oldAltOrientation = mAltOrientation;
+        int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation);
+        final boolean rotateSeamlessly;
+
+        if (mService.mPolicy.shouldRotateSeamlessly(oldRotation, rotation)) {
+            final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
+            if (seamlessRotated != null) {
+                // We can't rotate (seamlessly or not) while waiting for the last seamless rotation
+                // to complete (that is, waiting for windows to redraw). It's tempting to check
+                // w.mSeamlessRotationCount but that could be incorrect in the case of
+                // window-removal.
+                return false;
+            }
+
+            final WindowState cantSeamlesslyRotate = getWindow((w) ->
+                    w.isChildWindow() && w.isVisibleNow()
+                            && !w.mWinAnimator.mSurfaceController.getTransformToDisplayInverse());
+            if (cantSeamlesslyRotate != null) {
+                // In what can only be called an unfortunate workaround we require seamlessly
+                // rotated child windows to have the TRANSFORM_TO_DISPLAY_INVERSE flag. Due to
+                // limitations in the client API, there is no way for the client to set this flag in
+                // a race free fashion. If we seamlessly rotate a window which does not have this
+                // flag, but then gains it, we will get an incorrect visual result
+                // (rotated viewfinder). This means if we want to support seamlessly rotating
+                // windows which could gain this flag, we can't rotate windows without it. This
+                // limits seamless rotation in N to camera framework users, windows without
+                // children, and native code. This is unfortunate but having the camera work is our
+                // primary goal.
+                rotateSeamlessly = false;
+            } else {
+                rotateSeamlessly = true;
+            }
+        } else {
+            rotateSeamlessly = false;
+        }
+
+        // TODO: Implement forced rotation changes.
+        //       Set mAltOrientation to indicate that the application is receiving
+        //       an orientation that has different metrics than it expected.
+        //       eg. Portrait instead of Landscape.
+
+        final boolean altOrientation = !mService.mPolicy.rotationHasCompatibleMetricsLw(
+                lastOrientation, rotation);
+
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Selected orientation " + lastOrientation
+                + ", got rotation " + rotation + " which has "
+                + (altOrientation ? "incompatible" : "compatible") + " metrics");
+
+        if (oldRotation == rotation && oldAltOrientation == altOrientation) {
+            // No change.
+            return false;
+        }
+
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Rotation changed to " + rotation
+                + (altOrientation ? " (alt)" : "") + " from " + oldRotation
+                + (oldAltOrientation ? " (alt)" : "") + ", lastOrientation=" + lastOrientation);
+
+        if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
+            mService.mWaitingForConfig = true;
+        }
+
+        mRotation = rotation;
+        mAltOrientation = altOrientation;
+        if (isDefaultDisplay) {
+            mService.mPolicy.setRotationLw(rotation);
+        }
+
+        mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
+        mService.mH.removeMessages(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT);
+        mService.mH.sendEmptyMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
+                WINDOW_FREEZE_TIMEOUT_DURATION);
+
+        setLayoutNeeded();
+        final int[] anim = new int[2];
+        if (isDimming()) {
+            anim[0] = anim[1] = 0;
+        } else {
+            mService.mPolicy.selectRotationAnimationLw(anim);
+        }
+
+        if (!rotateSeamlessly) {
+            mService.startFreezingDisplayLocked(inTransaction, anim[0], anim[1]);
+            // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
+            screenRotationAnimation = mService.mAnimator.getScreenRotationAnimationLocked(
+                    mDisplayId);
+        } else {
+            // The screen rotation animation uses a screenshot to freeze the screen
+            // while windows resize underneath.
+            // When we are rotating seamlessly, we allow the elements to transition
+            // to their rotated state independently and without a freeze required.
+            screenRotationAnimation = null;
+
+            // We have to reset this in case a window was removed before it
+            // finished seamless rotation.
+            mService.mSeamlessRotationCount = 0;
+        }
+
+        // We need to update our screen size information to match the new rotation. If the rotation
+        // has actually changed then this method will return true and, according to the comment at
+        // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
+        // By updating the Display info here it will be available to
+        // #computeScreenConfiguration() later.
+        updateDisplayAndOrientation(getConfiguration().uiMode);
+
+        if (!inTransaction) {
+            if (SHOW_TRANSACTIONS) {
+                Slog.i(TAG_WM, ">>> OPEN TRANSACTION setRotationUnchecked");
+            }
+            mService.openSurfaceTransaction();
+        }
+        try {
+            // NOTE: We disable the rotation in the emulator because
+            //       it doesn't support hardware OpenGL emulation yet.
+            if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
+                    && screenRotationAnimation.hasScreenshot()) {
+                if (screenRotationAnimation.setRotationInTransaction(
+                        rotation, mService.mFxSession,
+                        MAX_ANIMATION_DURATION, mService.getTransitionAnimationScaleLocked(),
+                        mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) {
+                    mService.scheduleAnimationLocked();
+                }
+            }
+
+            if (rotateSeamlessly) {
+                forAllWindows(w -> {
+                    w.mWinAnimator.seamlesslyRotateWindow(oldRotation, rotation);
+                }, true /* traverseTopToBottom */);
+            }
+
+            mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
+        } finally {
+            if (!inTransaction) {
+                mService.closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) {
+                    Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
+                }
+            }
+        }
+
+        forAllWindows(w -> {
+            // Discard surface after orientation change, these can't be reused.
+            if (w.mAppToken != null) {
+                w.mAppToken.destroySavedSurfaces();
+            }
+            if (w.mHasSurface && !rotateSeamlessly) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Set mOrientationChanging of " + w);
+                w.mOrientationChanging = true;
+                mService.mRoot.mOrientationChangeComplete = false;
+                w.mLastFreezeDuration = 0;
+            }
+            w.mReportOrientationChanged = true;
+        }, true /* traverseTopToBottom */);
+
+        if (rotateSeamlessly) {
+            mService.mH.removeMessages(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT);
+            mService.mH.sendEmptyMessageDelayed(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT,
+                    SEAMLESS_ROTATION_TIMEOUT_DURATION);
+        }
+
+        for (int i = mService.mRotationWatchers.size() - 1; i >= 0; i--) {
+            final WindowManagerService.RotationWatcher rotationWatcher
+                    = mService.mRotationWatchers.get(i);
+            if (rotationWatcher.mDisplayId == mDisplayId) {
+                try {
+                    rotationWatcher.mWatcher.onRotationChanged(rotation);
+                } catch (RemoteException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        // TODO (multi-display): Magnification is supported only for the default display.
+        // Announce rotation only if we will not animate as we already have the
+        // windows in final state. Otherwise, we make this call at the rotation end.
+        if (screenRotationAnimation == null && mService.mAccessibilityController != null
+                && isDefaultDisplay) {
+            mService.mAccessibilityController.onRotationChangedLocked(this);
+        }
+
+        return true;
+    }
+
+    /**
+     * Update {@link #mDisplayInfo} and other internal variables when display is rotated or config
+     * changed.
+     * Do not call if {@link WindowManagerService#mDisplayReady} == false.
+     */
+    private DisplayInfo updateDisplayAndOrientation(int uiMode) {
+        // Use the effective "visual" dimensions based on current rotation
+        final boolean rotated = (mRotation == ROTATION_90 || mRotation == ROTATION_270);
+        final int realdw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
+        final int realdh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
+        int dw = realdw;
+        int dh = realdh;
+
+        if (mAltOrientation) {
+            if (realdw > realdh) {
+                // Turn landscape into portrait.
+                int maxw = (int)(realdh/1.3f);
+                if (maxw < realdw) {
+                    dw = maxw;
+                }
+            } else {
+                // Turn portrait into landscape.
+                int maxh = (int)(realdw/1.3f);
+                if (maxh < realdh) {
+                    dh = maxh;
+                }
+            }
+        }
+
+        // Update application display metrics.
+        final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode,
+                mDisplayId);
+        final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode,
+                mDisplayId);
+        mDisplayInfo.rotation = mRotation;
+        mDisplayInfo.logicalWidth = dw;
+        mDisplayInfo.logicalHeight = dh;
+        mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity;
+        mDisplayInfo.appWidth = appWidth;
+        mDisplayInfo.appHeight = appHeight;
+        if (isDefaultDisplay) {
+            mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics,
+                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+        }
+        mDisplayInfo.getAppMetrics(mDisplayMetrics);
+        if (mDisplayScalingDisabled) {
+            mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
+        } else {
+            mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
+        }
+
+        mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
+                mDisplayInfo);
+
+        mBaseDisplayRect.set(0, 0, dw, dh);
+
+        if (isDefaultDisplay) {
+            mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
+                    mCompatDisplayMetrics);
+        }
+        return mDisplayInfo;
+    }
+
+    /**
+     * Compute display configuration based on display properties and policy settings.
+     * Do not call if mDisplayReady == false.
+     */
+    void computeScreenConfiguration(Configuration config) {
+        final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode);
+
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+        config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT :
+                Configuration.ORIENTATION_LANDSCAPE;
+        config.screenWidthDp =
+                (int)(mService.mPolicy.getConfigDisplayWidth(dw, dh, displayInfo.rotation,
+                        config.uiMode, mDisplayId) / mDisplayMetrics.density);
+        config.screenHeightDp =
+                (int)(mService.mPolicy.getConfigDisplayHeight(dw, dh, displayInfo.rotation,
+                        config.uiMode, mDisplayId) / mDisplayMetrics.density);
+        final boolean rotated = (displayInfo.rotation == Surface.ROTATION_90
+                || displayInfo.rotation == Surface.ROTATION_270);
+
+        computeSizeRangesAndScreenLayout(displayInfo, mDisplayId, rotated, config.uiMode, dw, dh,
+                mDisplayMetrics.density, config);
+
+        config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
+                | ((displayInfo.flags & Display.FLAG_ROUND) != 0
+                ? Configuration.SCREENLAYOUT_ROUND_YES
+                : Configuration.SCREENLAYOUT_ROUND_NO);
+
+        config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
+        config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
+        config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, config.uiMode, dw,
+                dh, mDisplayId);
+        config.densityDpi = displayInfo.logicalDensityDpi;
+
+        config.colorMode =
+                (displayInfo.isHdr()
+                        ? Configuration.COLOR_MODE_HDR_YES
+                        : Configuration.COLOR_MODE_HDR_NO)
+                        | (displayInfo.isWideColorGamut()
+                        ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES
+                        : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO);
+
+        // Update the configuration based on available input devices, lid switch,
+        // and platform configuration.
+        config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
+        config.keyboard = Configuration.KEYBOARD_NOKEYS;
+        config.navigation = Configuration.NAVIGATION_NONAV;
+
+        int keyboardPresence = 0;
+        int navigationPresence = 0;
+        final InputDevice[] devices = mService.mInputManager.getInputDevices();
+        final int len = devices != null ? devices.length : 0;
+        for (int i = 0; i < len; i++) {
+            InputDevice device = devices[i];
+            if (!device.isVirtual()) {
+                final int sources = device.getSources();
+                final int presenceFlag = device.isExternal() ?
+                        WindowManagerPolicy.PRESENCE_EXTERNAL :
+                        WindowManagerPolicy.PRESENCE_INTERNAL;
+
+                // TODO(multi-display): Configure on per-display basis.
+                if (mService.mIsTouchDevice) {
+                    if ((sources & InputDevice.SOURCE_TOUCHSCREEN) ==
+                            InputDevice.SOURCE_TOUCHSCREEN) {
+                        config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
+                    }
+                } else {
+                    config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
+                }
+
+                if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
+                    config.navigation = Configuration.NAVIGATION_TRACKBALL;
+                    navigationPresence |= presenceFlag;
+                } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
+                        && config.navigation == Configuration.NAVIGATION_NONAV) {
+                    config.navigation = Configuration.NAVIGATION_DPAD;
+                    navigationPresence |= presenceFlag;
+                }
+
+                if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
+                    config.keyboard = Configuration.KEYBOARD_QWERTY;
+                    keyboardPresence |= presenceFlag;
+                }
+            }
+        }
+
+        if (config.navigation == Configuration.NAVIGATION_NONAV && mService.mHasPermanentDpad) {
+            config.navigation = Configuration.NAVIGATION_DPAD;
+            navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL;
+        }
+
+        // Determine whether a hard keyboard is available and enabled.
+        // TODO(multi-display): Should the hardware keyboard be tied to a display or to a device?
+        boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
+        if (hardKeyboardAvailable != mService.mHardKeyboardAvailable) {
+            mService.mHardKeyboardAvailable = hardKeyboardAvailable;
+            mService.mH.removeMessages(WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+            mService.mH.sendEmptyMessage(WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+        }
+
+        // Let the policy update hidden states.
+        config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
+        config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
+        config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
+        mService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
+    }
+
+    private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh,
+            int displayId) {
+        mTmpDisplayMetrics.setTo(mDisplayMetrics);
+        final DisplayMetrics tmpDm = mTmpDisplayMetrics;
+        final int unrotDw, unrotDh;
+        if (rotated) {
+            unrotDw = dh;
+            unrotDh = dw;
+        } else {
+            unrotDw = dw;
+            unrotDh = dh;
+        }
+        int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh,
+                displayId);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw,
+                displayId);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh,
+                displayId);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw,
+                displayId);
+        return sw;
+    }
+
+    private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode,
+            DisplayMetrics dm, int dw, int dh, int displayId) {
+        dm.noncompatWidthPixels = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
+                displayId);
+        dm.noncompatHeightPixels = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation,
+                uiMode, displayId);
+        float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
+        int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f);
+        if (curSize == 0 || size < curSize) {
+            curSize = size;
+        }
+        return curSize;
+    }
+
+    private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, int displayId,
+            boolean rotated, int uiMode, int dw, int dh, float density, Configuration outConfig) {
+
+        // We need to determine the smallest width that will occur under normal
+        // operation.  To this, start with the base screen size and compute the
+        // width under the different possible rotations.  We need to un-rotate
+        // the current screen dimensions before doing this.
+        int unrotDw, unrotDh;
+        if (rotated) {
+            unrotDw = dh;
+            unrotDh = dw;
+        } else {
+            unrotDw = dw;
+            unrotDh = dh;
+        }
+        displayInfo.smallestNominalAppWidth = 1<<30;
+        displayInfo.smallestNominalAppHeight = 1<<30;
+        displayInfo.largestNominalAppWidth = 0;
+        displayInfo.largestNominalAppHeight = 0;
+        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_0, uiMode, unrotDw,
+                unrotDh);
+        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_90, uiMode, unrotDh,
+                unrotDw);
+        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_180, uiMode, unrotDw,
+                unrotDh);
+        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_270, uiMode, unrotDh,
+                unrotDw);
+        int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode,
+                displayId);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode,
+                displayId);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode,
+                displayId);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode,
+                displayId);
+        outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density);
+        outConfig.screenLayout = sl;
+    }
+
+    private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh,
+            int uiMode, int displayId) {
+        // Get the app screen size at this rotation.
+        int w = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayId);
+        int h = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayId);
+
+        // Compute the screen layout size class for this rotation.
+        int longSize = w;
+        int shortSize = h;
+        if (longSize < shortSize) {
+            int tmp = longSize;
+            longSize = shortSize;
+            shortSize = tmp;
+        }
+        longSize = (int)(longSize/density);
+        shortSize = (int)(shortSize/density);
+        return Configuration.reduceScreenLayout(curLayout, longSize, shortSize);
+    }
+
+    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int displayId, int rotation,
+            int uiMode, int dw, int dh) {
+        final int width = mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode,
+                displayId);
+        if (width < displayInfo.smallestNominalAppWidth) {
+            displayInfo.smallestNominalAppWidth = width;
+        }
+        if (width > displayInfo.largestNominalAppWidth) {
+            displayInfo.largestNominalAppWidth = width;
+        }
+        final int height = mService.mPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode,
+                displayId);
+        if (height < displayInfo.smallestNominalAppHeight) {
+            displayInfo.smallestNominalAppHeight = height;
+        }
+        if (height > displayInfo.largestNominalAppHeight) {
+            displayInfo.largestNominalAppHeight = height;
+        }
+    }
+
     DockedStackDividerController getDockedDividerController() {
         return mDividerControllerLocked;
     }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0222b3d..68d0f24 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -55,13 +55,11 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
@@ -740,7 +738,7 @@
             if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
             // TODO(multi-display): Update rotation for different displays separately.
             final int displayId = defaultDisplay.getDisplayId();
-            if (mService.updateRotationUncheckedLocked(false, displayId)) {
+            if (defaultDisplay.updateRotationUnchecked(false /* inTransaction */)) {
                 mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
             } else {
                 mUpdateRotation = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 64614fe..b063e01 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -111,7 +111,6 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -592,11 +591,6 @@
 
     boolean mIsTouchDevice;
 
-    final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
-    final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
-    final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
-    final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
-
     final H mH = new H();
 
     final Choreographer mChoreographer = Choreographer.getInstance();
@@ -841,9 +835,6 @@
 
     final Configuration mTempConfiguration = new Configuration();
 
-    // The desired scaling factor for compatible apps.
-    float mCompatibleScreenScale;
-
     // If true, only the core apps and services are being launched because the device
     // is in a special boot mode, such as being encrypted or waiting for a decryption password.
     // For example, when this flag is true, there will be no wallpaper service.
@@ -2461,10 +2452,10 @@
             // to keep override configs clear of non-empty values (e.g. fontSize).
             mTempConfiguration.unset();
             mTempConfiguration.updateFrom(currentConfig);
-            computeScreenConfigurationLocked(mTempConfiguration, displayId);
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            displayContent.computeScreenConfiguration(mTempConfiguration);
             if (currentConfig.diff(mTempConfiguration) != 0) {
                 mWaitingForConfig = true;
-                final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
                 displayContent.setLayoutNeeded();
                 int anim[] = new int[2];
                 if (displayContent.isDimming()) {
@@ -2505,7 +2496,7 @@
                 if (dc.isDefaultDisplay) {
                     mPolicy.setCurrentOrientationLw(req);
                 }
-                if (updateRotationUncheckedLocked(inTransaction, displayId)) {
+                if (dc.updateRotationUnchecked(inTransaction)) {
                     // changed
                     return true;
                 }
@@ -3843,10 +3834,12 @@
             mDeferredRotationPauseCount -= 1;
             if (mDeferredRotationPauseCount == 0) {
                 // TODO(multi-display): Update rotation for different displays separately.
-                final int displayId = DEFAULT_DISPLAY;
-                final boolean changed = updateRotationUncheckedLocked(false, displayId);
+                final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                final boolean changed = displayContent.updateRotationUnchecked(
+                        false /* inTransaction */);
                 if (changed) {
-                    mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+                    mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayContent.getDisplayId())
+                            .sendToTarget();
                 }
             }
         }
@@ -3862,9 +3855,10 @@
         try {
             final boolean rotationChanged;
             // TODO(multi-display): Update rotation for different displays separately.
-            int displayId = DEFAULT_DISPLAY;
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
             synchronized (mWindowMap) {
-                rotationChanged = updateRotationUncheckedLocked(false, displayId);
+                rotationChanged = displayContent.updateRotationUnchecked(
+                        false /* inTransaction */);
                 if (!rotationChanged || forceRelayout) {
                     getDefaultDisplayContentLocked().setLayoutNeeded();
                     mWindowPlacerLocked.performSurfacePlacement();
@@ -3872,234 +3866,13 @@
             }
 
             if (rotationChanged || alwaysSendConfiguration) {
-                sendNewConfiguration(displayId);
+                sendNewConfiguration(displayContent.getDisplayId());
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
-
-    // TODO(multidisplay): Rotate any display? Move to DisplayContent
-    /**
-     * Updates the current rotation of the specified display.
-     *
-     * Returns true if the rotation has been changed.  In this case YOU MUST CALL
-     * {@link #sendNewConfiguration(int)} TO UNFREEZE THE SCREEN.
-     */
-    boolean updateRotationUncheckedLocked(boolean inTransaction, int displayId) {
-        if (mDeferredRotationPauseCount > 0) {
-            // Rotation updates have been paused temporarily.  Defer the update until
-            // updates have been resumed.
-            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");
-            return false;
-        }
-
-        ScreenRotationAnimation screenRotationAnimation =
-                mAnimator.getScreenRotationAnimationLocked(displayId);
-        if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
-            // Rotation updates cannot be performed while the previous rotation change
-            // animation is still in progress.  Skip this update.  We will try updating
-            // again after the animation is finished and the display is unfrozen.
-            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress.");
-            return false;
-        }
-        if (mDisplayFrozen) {
-            // Even if the screen rotation animation has finished (e.g. isAnimating
-            // returns false), there is still some time where we haven't yet unfrozen
-            // the display. We also need to abort rotation here.
-            if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
-                    "Deferring rotation, still finishing previous rotation");
-            return false;
-        }
-
-        if (!mDisplayEnabled) {
-            // No point choosing a rotation if the display is not enabled.
-            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled.");
-            return false;
-        }
-
-        final DisplayContent dc = mRoot.getDisplayContent(displayId);
-
-        final int oldRotation = dc.getRotation();
-        final int lastOrientation = dc.getLastOrientation();
-        final boolean oldAltOrientation = dc.getAltOrientation();
-        int rotation = mPolicy.rotationForOrientationLw(lastOrientation, oldRotation);
-        final boolean rotateSeamlessly;
-
-        if (mPolicy.shouldRotateSeamlessly(oldRotation, rotation)) {
-            final WindowState seamlessRotated = dc.getWindow((w) -> w.mSeamlesslyRotated);
-            if (seamlessRotated != null) {
-                // We can't rotate (seamlessly or not) while waiting for the last seamless rotation
-                // to complete (that is, waiting for windows to redraw). It's tempting to check
-                // w.mSeamlessRotationCount but that could be incorrect in the case of
-                // window-removal.
-                return false;
-            }
-
-            final WindowState cantSeamlesslyRotate = dc.getWindow((w) ->
-                    w.isChildWindow() && w.isVisibleNow()
-                            && !w.mWinAnimator.mSurfaceController.getTransformToDisplayInverse());
-            if (cantSeamlesslyRotate != null) {
-                // In what can only be called an unfortunate workaround we require seamlessly
-                // rotated child windows to have the TRANSFORM_TO_DISPLAY_INVERSE flag. Due to
-                // limitations in the client API, there is no way for the client to set this flag in
-                // a race free fashion. If we seamlessly rotate a window which does not have this
-                // flag, but then gains it, we will get an incorrect visual result
-                // (rotated viewfinder). This means if we want to support seamlessly rotating
-                // windows which could gain this flag, we can't rotate windows without it. This
-                // limits seamless rotation in N to camera framework users, windows without
-                // children, and native code. This is unfortunate but having the camera work is our
-                // primary goal.
-                rotateSeamlessly = false;
-            } else {
-                rotateSeamlessly = true;
-            }
-        } else {
-            rotateSeamlessly = false;
-        }
-
-        // TODO: Implement forced rotation changes.
-        //       Set mAltOrientation to indicate that the application is receiving
-        //       an orientation that has different metrics than it expected.
-        //       eg. Portrait instead of Landscape.
-
-        boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw(lastOrientation, rotation);
-
-        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Selected orientation " + lastOrientation
-                + ", got rotation " + rotation + " which has "
-                + (altOrientation ? "incompatible" : "compatible") + " metrics");
-
-        if (oldRotation == rotation && oldAltOrientation == altOrientation) {
-            // No change.
-            return false;
-        }
-
-        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Rotation changed to " + rotation
-                + (altOrientation ? " (alt)" : "") + " from " + oldRotation
-                + (oldAltOrientation ? " (alt)" : "") + ", lastOrientation=" + lastOrientation);
-
-        if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
-            mWaitingForConfig = true;
-        }
-
-        dc.setRotation(rotation);
-        dc.setAltOrientation(altOrientation);
-        if (dc.isDefaultDisplay) {
-            mPolicy.setRotationLw(rotation);
-        }
-
-        mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
-        mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
-        mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
-
-        dc.setLayoutNeeded();
-        final int[] anim = new int[2];
-        if (dc.isDimming()) {
-            anim[0] = anim[1] = 0;
-        } else {
-            mPolicy.selectRotationAnimationLw(anim);
-        }
-
-        if (!rotateSeamlessly) {
-            startFreezingDisplayLocked(inTransaction, anim[0], anim[1]);
-            // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
-            screenRotationAnimation = mAnimator.getScreenRotationAnimationLocked(displayId);
-        } else {
-            // The screen rotation animation uses a screenshot to freeze the screen
-            // while windows resize underneath.
-            // When we are rotating seamlessly, we allow the elements to transition
-            // to their rotated state independently and without a freeze required.
-            screenRotationAnimation = null;
-
-            // We have to reset this in case a window was removed before it
-            // finished seamless rotation.
-            mSeamlessRotationCount = 0;
-        }
-
-        // We need to update our screen size information to match the new rotation. If the rotation
-        // has actually changed then this method will return true and, according to the comment at
-        // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
-        // By updating the Display info here it will be available to
-        // computeScreenConfigurationLocked later.
-        updateDisplayAndOrientationLocked(dc.getConfiguration().uiMode, displayId);
-
-        final DisplayInfo displayInfo = dc.getDisplayInfo();
-        if (!inTransaction) {
-            if (SHOW_TRANSACTIONS) {
-                Slog.i(TAG_WM, ">>> OPEN TRANSACTION setRotationUnchecked");
-            }
-            openSurfaceTransaction();
-        }
-        try {
-            // NOTE: We disable the rotation in the emulator because
-            //       it doesn't support hardware OpenGL emulation yet.
-            if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
-                    && screenRotationAnimation.hasScreenshot()) {
-                if (screenRotationAnimation.setRotationInTransaction(
-                        rotation, mFxSession,
-                        MAX_ANIMATION_DURATION, getTransitionAnimationScaleLocked(),
-                        displayInfo.logicalWidth, displayInfo.logicalHeight)) {
-                    scheduleAnimationLocked();
-                }
-            }
-
-            if (rotateSeamlessly) {
-                dc.forAllWindows(w -> {
-                        w.mWinAnimator.seamlesslyRotateWindow(oldRotation, rotation);
-                }, true /* traverseTopToBottom */);
-            }
-
-            mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
-        } finally {
-            if (!inTransaction) {
-                closeSurfaceTransaction();
-                if (SHOW_LIGHT_TRANSACTIONS) {
-                    Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
-                }
-            }
-        }
-
-        dc.forAllWindows(w -> {
-            // Discard surface after orientation change, these can't be reused.
-            if (w.mAppToken != null) {
-                w.mAppToken.destroySavedSurfaces();
-            }
-            if (w.mHasSurface && !rotateSeamlessly) {
-                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Set mOrientationChanging of " + w);
-                w.mOrientationChanging = true;
-                mRoot.mOrientationChangeComplete = false;
-                w.mLastFreezeDuration = 0;
-            }
-            w.mReportOrientationChanged = true;
-        }, true /* traverseTopToBottom */);
-
-        if (rotateSeamlessly) {
-            mH.removeMessages(H.SEAMLESS_ROTATION_TIMEOUT);
-            mH.sendEmptyMessageDelayed(H.SEAMLESS_ROTATION_TIMEOUT, SEAMLESS_ROTATION_TIMEOUT_DURATION);
-        }
-
-        for (int i = mRotationWatchers.size() - 1; i >= 0; i--) {
-            final RotationWatcher rotationWatcher = mRotationWatchers.get(i);
-            if (rotationWatcher.mDisplayId == displayId) {
-                try {
-                    rotationWatcher.mWatcher.onRotationChanged(rotation);
-                } catch (RemoteException e) {
-                }
-            }
-        }
-
-        // TODO (multidisplay): Magnification is supported only for the default display.
-        // Announce rotation only if we will not animate as we already have the
-        // windows in final state. Otherwise, we make this call at the rotation end.
-        if (screenRotationAnimation == null && mAccessibilityController != null
-                && dc.getDisplayId() == DEFAULT_DISPLAY) {
-            mAccessibilityController.onRotationChangedLocked(getDefaultDisplayContentLocked());
-        }
-
-        return true;
-    }
-
     @Override
     public int getDefaultDisplayRotation() {
         synchronized (mWindowMap) {
@@ -4572,7 +4345,7 @@
                 // Something changed (E.g. device rotation), but no configuration update is needed.
                 // E.g. changing device rotation by 180 degrees. Go ahead and perform surface
                 // placement to unfreeze the display since we froze it when the rotation was updated
-                // in updateRotationUncheckedLocked.
+                // in DisplayContent#updateRotationUnchecked.
                 synchronized (mWindowMap) {
                     if (mWaitingForConfig) {
                         mWaitingForConfig = false;
@@ -4597,297 +4370,9 @@
             return null;
         }
         final Configuration config = new Configuration();
-        computeScreenConfigurationLocked(config, displayId);
-        return config;
-    }
-
-    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int displayId, int rotation,
-            int uiMode, int dw, int dh) {
-        final int width = mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode, displayId);
-        if (width < displayInfo.smallestNominalAppWidth) {
-            displayInfo.smallestNominalAppWidth = width;
-        }
-        if (width > displayInfo.largestNominalAppWidth) {
-            displayInfo.largestNominalAppWidth = width;
-        }
-        final int height = mPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode, displayId);
-        if (height < displayInfo.smallestNominalAppHeight) {
-            displayInfo.smallestNominalAppHeight = height;
-        }
-        if (height > displayInfo.largestNominalAppHeight) {
-            displayInfo.largestNominalAppHeight = height;
-        }
-    }
-
-    private int reduceConfigLayout(int curLayout, int rotation, float density,
-            int dw, int dh, int uiMode, int displayId) {
-        // Get the app screen size at this rotation.
-        int w = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayId);
-        int h = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayId);
-
-        // Compute the screen layout size class for this rotation.
-        int longSize = w;
-        int shortSize = h;
-        if (longSize < shortSize) {
-            int tmp = longSize;
-            longSize = shortSize;
-            shortSize = tmp;
-        }
-        longSize = (int)(longSize/density);
-        shortSize = (int)(shortSize/density);
-        return Configuration.reduceScreenLayout(curLayout, longSize, shortSize);
-    }
-
-    private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, int displayId,
-            boolean rotated, int uiMode, int dw, int dh, float density, Configuration outConfig) {
-
-        // We need to determine the smallest width that will occur under normal
-        // operation.  To this, start with the base screen size and compute the
-        // width under the different possible rotations.  We need to un-rotate
-        // the current screen dimensions before doing this.
-        int unrotDw, unrotDh;
-        if (rotated) {
-            unrotDw = dh;
-            unrotDh = dw;
-        } else {
-            unrotDw = dw;
-            unrotDh = dh;
-        }
-        displayInfo.smallestNominalAppWidth = 1<<30;
-        displayInfo.smallestNominalAppHeight = 1<<30;
-        displayInfo.largestNominalAppWidth = 0;
-        displayInfo.largestNominalAppHeight = 0;
-        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_0, uiMode, unrotDw,
-                unrotDh);
-        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_90, uiMode, unrotDh,
-                unrotDw);
-        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_180, uiMode, unrotDw,
-                unrotDh);
-        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_270, uiMode, unrotDh,
-                unrotDw);
-        int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode,
-                displayId);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode,
-                displayId);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode,
-                displayId);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode,
-                displayId);
-        outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density);
-        outConfig.screenLayout = sl;
-    }
-
-    private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode,
-            DisplayMetrics dm, int dw, int dh, int displayId) {
-        dm.noncompatWidthPixels = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
-                displayId);
-        dm.noncompatHeightPixels = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
-                displayId);
-        float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
-        int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f);
-        if (curSize == 0 || size < curSize) {
-            curSize = size;
-        }
-        return curSize;
-    }
-
-    private int computeCompatSmallestWidth(boolean rotated, int uiMode, DisplayMetrics dm, int dw,
-            int dh, int displayId) {
-        mTmpDisplayMetrics.setTo(dm);
-        final DisplayMetrics tmpDm = mTmpDisplayMetrics;
-        final int unrotDw, unrotDh;
-        if (rotated) {
-            unrotDw = dh;
-            unrotDh = dw;
-        } else {
-            unrotDw = dw;
-            unrotDh = dh;
-        }
-        int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh,
-                displayId);
-        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw,
-                displayId);
-        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh,
-                displayId);
-        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw,
-                displayId);
-        return sw;
-    }
-
-    /** Do not call if mDisplayReady == false */
-    private DisplayInfo updateDisplayAndOrientationLocked(int uiMode, int displayId) {
         final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
-
-        // TODO(multi-display): Implement rotation for secondary displays.
-        final boolean isDefaultDisplay = displayContent.isDefaultDisplay;
-        final int displayRotation = displayContent.getRotation();
-        final boolean altDisplayOrientation = displayContent.getAltOrientation();
-
-        // Use the effective "visual" dimensions based on current rotation
-        final boolean rotated = (displayRotation == Surface.ROTATION_90
-                || displayRotation == Surface.ROTATION_270);
-        final int realdw = rotated ?
-                displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth;
-        final int realdh = rotated ?
-                displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight;
-        int dw = realdw;
-        int dh = realdh;
-
-        if (altDisplayOrientation) {
-            if (realdw > realdh) {
-                // Turn landscape into portrait.
-                int maxw = (int)(realdh/1.3f);
-                if (maxw < realdw) {
-                    dw = maxw;
-                }
-            } else {
-                // Turn portrait into landscape.
-                int maxh = (int)(realdw/1.3f);
-                if (maxh < realdh) {
-                    dh = maxh;
-                }
-            }
-        }
-
-        // Update application display metrics.
-        final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, displayRotation, uiMode,
-                displayId);
-        final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, displayRotation, uiMode,
-                displayId);
-        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        displayInfo.rotation = displayRotation;
-        displayInfo.logicalWidth = dw;
-        displayInfo.logicalHeight = dh;
-        displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
-        displayInfo.appWidth = appWidth;
-        displayInfo.appHeight = appHeight;
-        if (isDefaultDisplay) {
-            displayInfo.getLogicalMetrics(mRealDisplayMetrics,
-                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
-        }
-        displayInfo.getAppMetrics(mDisplayMetrics);
-        if (displayContent.mDisplayScalingDisabled) {
-            displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
-        } else {
-            displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
-        }
-
-        mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
-                displayContent.getDisplayId(), displayInfo);
-
-        displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
-        if (false) {
-            Slog.i(TAG_WM, "Set app display size: " + appWidth + " x " + appHeight);
-        }
-
-        if (isDefaultDisplay) {
-            mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
-                    mCompatDisplayMetrics);
-        }
-        return displayInfo;
-    }
-
-    /** Do not call if mDisplayReady == false */
-    private void computeScreenConfigurationLocked(Configuration config, int displayId) {
-        final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(config.uiMode, displayId);
-
-        final int dw = displayInfo.logicalWidth;
-        final int dh = displayInfo.logicalHeight;
-        config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT :
-                Configuration.ORIENTATION_LANDSCAPE;
-        config.screenWidthDp =
-                (int)(mPolicy.getConfigDisplayWidth(dw, dh, displayInfo.rotation, config.uiMode,
-                        displayId) / mDisplayMetrics.density);
-        config.screenHeightDp =
-                (int)(mPolicy.getConfigDisplayHeight(dw, dh, displayInfo.rotation, config.uiMode,
-                        displayId) / mDisplayMetrics.density);
-        final boolean rotated = (displayInfo.rotation == Surface.ROTATION_90
-                || displayInfo.rotation == Surface.ROTATION_270);
-
-        computeSizeRangesAndScreenLayout(displayInfo, displayId, rotated, config.uiMode, dw, dh,
-                mDisplayMetrics.density, config);
-
-        config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
-                | ((displayInfo.flags & Display.FLAG_ROUND) != 0
-                        ? Configuration.SCREENLAYOUT_ROUND_YES
-                        : Configuration.SCREENLAYOUT_ROUND_NO);
-
-        config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
-        config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
-        config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, config.uiMode,
-                mDisplayMetrics, dw, dh, displayId);
-        config.densityDpi = displayInfo.logicalDensityDpi;
-
-        config.colorMode =
-                (displayInfo.isHdr()
-                        ? Configuration.COLOR_MODE_HDR_YES
-                        : Configuration.COLOR_MODE_HDR_NO)
-                | (displayInfo.isWideColorGamut()
-                        ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES
-                        : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO);
-
-        // Update the configuration based on available input devices, lid switch,
-        // and platform configuration.
-        config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
-        config.keyboard = Configuration.KEYBOARD_NOKEYS;
-        config.navigation = Configuration.NAVIGATION_NONAV;
-
-        int keyboardPresence = 0;
-        int navigationPresence = 0;
-        final InputDevice[] devices = mInputManager.getInputDevices();
-        final int len = devices != null ? devices.length : 0;
-        for (int i = 0; i < len; i++) {
-            InputDevice device = devices[i];
-            if (!device.isVirtual()) {
-                final int sources = device.getSources();
-                final int presenceFlag = device.isExternal() ?
-                        WindowManagerPolicy.PRESENCE_EXTERNAL :
-                                WindowManagerPolicy.PRESENCE_INTERNAL;
-
-                if (mIsTouchDevice) {
-                    if ((sources & InputDevice.SOURCE_TOUCHSCREEN) ==
-                            InputDevice.SOURCE_TOUCHSCREEN) {
-                        config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
-                    }
-                } else {
-                    config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
-                }
-
-                if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
-                    config.navigation = Configuration.NAVIGATION_TRACKBALL;
-                    navigationPresence |= presenceFlag;
-                } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
-                        && config.navigation == Configuration.NAVIGATION_NONAV) {
-                    config.navigation = Configuration.NAVIGATION_DPAD;
-                    navigationPresence |= presenceFlag;
-                }
-
-                if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
-                    config.keyboard = Configuration.KEYBOARD_QWERTY;
-                    keyboardPresence |= presenceFlag;
-                }
-            }
-        }
-
-        if (config.navigation == Configuration.NAVIGATION_NONAV && mHasPermanentDpad) {
-            config.navigation = Configuration.NAVIGATION_DPAD;
-            navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL;
-        }
-
-        // Determine whether a hard keyboard is available and enabled.
-        boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
-        if (hardKeyboardAvailable != mHardKeyboardAvailable) {
-            mHardKeyboardAvailable = hardKeyboardAvailable;
-            mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
-            mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
-        }
-
-        // Let the policy update hidden states.
-        config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
-        config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
-        config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
-        mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
+        displayContent.computeScreenConfiguration(config);
+        return config;
     }
 
     void notifyHardKeyboardStatusChange() {
@@ -6099,7 +5584,7 @@
                 displayId);
         final Configuration currentDisplayConfig = displayContent.getConfiguration();
         mTempConfiguration.setTo(currentDisplayConfig);
-        computeScreenConfigurationLocked(mTempConfiguration, displayId);
+        displayContent.computeScreenConfiguration(mTempConfiguration);
         configChanged |= currentDisplayConfig.diff(mTempConfiguration) != 0;
 
         if (configChanged) {
@@ -6562,7 +6047,8 @@
 
         if (updateRotation) {
             if (DEBUG_ORIENTATION) Slog.d(TAG_WM, "Performing post-rotate rotation");
-            configChanged |= updateRotationUncheckedLocked(false, displayId);
+            configChanged |= displayContent.updateRotationUnchecked(
+                    false /* inTransaction */);
         }
 
         if (configChanged) {
@@ -6605,8 +6091,9 @@
                 String[] toks = line.split("%");
                 if (toks != null && toks.length > 0) {
                     // TODO(multi-display): Show watermarks on secondary displays.
-                    mWatermark = new Watermark(getDefaultDisplayContentLocked().getDisplay(),
-                            mRealDisplayMetrics, mFxSession, toks);
+                    final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                    mWatermark = new Watermark(displayContent.getDisplay(),
+                            displayContent.mRealDisplayMetrics, mFxSession, toks);
                 }
             }
         } catch (FileNotFoundException e) {
@@ -7642,9 +7129,10 @@
             if (DEBUG_ORIENTATION) {
                 Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
             }
-            final int displayId = w.getDisplayId();
-            if (updateRotationUncheckedLocked(false, displayId)) {
-                mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+            final DisplayContent displayContent = w.getDisplayContent();
+            if (displayContent.updateRotationUnchecked(false /* inTransaction */)) {
+                mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayContent.getDisplayId())
+                        .sendToTarget();
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ccbc5ba..4e593d8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1273,8 +1273,8 @@
 
     void prelayout() {
         if (mEnforceSizeCompat) {
-            mGlobalScale = mService.mCompatibleScreenScale;
-            mInvGlobalScale = 1/mGlobalScale;
+            mGlobalScale = getDisplayContent().mCompatibleScreenScale;
+            mInvGlobalScale = 1 / mGlobalScale;
         } else {
             mGlobalScale = mInvGlobalScale = 1;
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7ad0292..0e6a542 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3141,6 +3141,10 @@
             throw new IllegalArgumentException("Only apps in internal storage can be active admin: "
                     + adminReceiver);
         }
+        if (info.getActivityInfo().applicationInfo.isInstantApp()) {
+            throw new IllegalArgumentException("Instant apps cannot be device admins: "
+                    + adminReceiver);
+        }
         synchronized (this) {
             long ident = mInjector.binderClearCallingIdentity();
             try {
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 0a90749..a0a9310 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -93,17 +93,10 @@
     class ReceiveThread extends Thread {
         private final byte[] mPacket = new byte[1514];
         private final FileDescriptor mSocket;
-        private volatile boolean mStopped;
-
-        // Starting time of the RA receiver thread.
         private final long mStart = SystemClock.elapsedRealtime();
+        private final ApfStats mStats = new ApfStats();
 
-        private int mReceivedRas;     // Number of received RAs
-        private int mMatchingRas;     // Number of received RAs matching a known RA
-        private int mDroppedRas;      // Number of received RAs ignored due to the MAX_RAS limit
-        private int mParseErrors;     // Number of received RAs that could not be parsed
-        private int mZeroLifetimeRas; // Number of received RAs with a 0 lifetime
-        private int mProgramUpdates;  // Number of APF program updates triggered by receiving a RA
+        private volatile boolean mStopped;
 
         public ReceiveThread(FileDescriptor socket) {
             mSocket = socket;
@@ -134,35 +127,40 @@
         }
 
         private void updateStats(ProcessRaResult result) {
-            mReceivedRas++;
+            mStats.receivedRas++;
             switch(result) {
                 case MATCH:
-                    mMatchingRas++;
+                    mStats.matchingRas++;
                     return;
                 case DROPPED:
-                    mDroppedRas++;
+                    mStats.droppedRas++;
                     return;
                 case PARSE_ERROR:
-                    mParseErrors++;
+                    mStats.parseErrors++;
                     return;
                 case ZERO_LIFETIME:
-                    mZeroLifetimeRas++;
+                    mStats.zeroLifetimeRas++;
                     return;
                 case UPDATE_EXPIRY:
-                    mMatchingRas++;
-                    mProgramUpdates++;
+                    mStats.matchingRas++;
+                    mStats.programUpdates++;
                     return;
                 case UPDATE_NEW_RA:
-                    mProgramUpdates++;
+                    mStats.programUpdates++;
                     return;
             }
         }
 
         private void logStats() {
-            long durationMs = SystemClock.elapsedRealtime() - mStart;
-            int maxSize = mApfCapabilities.maximumApfProgramSize;
-            mMetricsLog.log(new ApfStats(durationMs, mReceivedRas, mMatchingRas, mDroppedRas,
-                     mZeroLifetimeRas, mParseErrors, mProgramUpdates, maxSize));
+            final long nowMs = SystemClock.elapsedRealtime();
+            synchronized (this) {
+                mStats.durationMs = nowMs - mStart;
+                mStats.maxProgramSize = mApfCapabilities.maximumApfProgramSize;
+                mStats.programUpdatesAll = mNumProgramUpdates;
+                mStats.programUpdatesAllowingMulticast = mNumProgramUpdatesAllowingMulticast;
+                mMetricsLog.log(mStats);
+                logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS);
+            }
         }
     }
 
@@ -218,6 +216,8 @@
             4,    // Protocol size: 4
     };
     private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
+    // Do not log ApfProgramEvents whose actual lifetimes was less than this.
+    private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2;
 
     private final ApfCapabilities mApfCapabilities;
     private final IpManager.Callback mIpManagerCallback;
@@ -247,6 +247,7 @@
         mMulticastFilter = multicastFilter;
         mMetricsLog = log;
 
+        // TODO: ApfFilter should not generate programs until IpManager sends provisioning success.
         maybeStartFilter();
     }
 
@@ -661,14 +662,19 @@
     // How long should the last installed filter program live for? In seconds.
     @GuardedBy("this")
     private long mLastInstalledProgramMinLifetime;
+    @GuardedBy("this")
+    private ApfProgramEvent mLastInstallEvent;
 
     // For debugging only. The last program installed.
     @GuardedBy("this")
     private byte[] mLastInstalledProgram;
 
-    // For debugging only. How many times the program was updated since we started.
+    // How many times the program was updated since we started.
     @GuardedBy("this")
-    private int mNumProgramUpdates;
+    private int mNumProgramUpdates = 0;
+    // How many times the program was updated since we started for allowing multicast traffic.
+    @GuardedBy("this")
+    private int mNumProgramUpdatesAllowingMulticast = 0;
 
     /**
      * Generate filter code to process ARP packets. Execution of this code ends in either the
@@ -947,7 +953,8 @@
             Log.e(TAG, "Failed to generate APF program.", e);
             return;
         }
-        mLastTimeInstalledProgram = currentTimeSeconds();
+        final long now = currentTimeSeconds();
+        mLastTimeInstalledProgram = now;
         mLastInstalledProgramMinLifetime = programMinLifetime;
         mLastInstalledProgram = program;
         mNumProgramUpdates++;
@@ -956,9 +963,26 @@
             hexDump("Installing filter: ", program, program.length);
         }
         mIpManagerCallback.installPacketFilter(program);
-        int flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter);
-        mMetricsLog.log(new ApfProgramEvent(
-                programMinLifetime, rasToFilter.size(), mRas.size(), program.length, flags));
+        logApfProgramEventLocked(now);
+        mLastInstallEvent = new ApfProgramEvent();
+        mLastInstallEvent.lifetime = programMinLifetime;
+        mLastInstallEvent.filteredRas = rasToFilter.size();
+        mLastInstallEvent.currentRas = mRas.size();
+        mLastInstallEvent.programLength = program.length;
+        mLastInstallEvent.flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter);
+    }
+
+    private void logApfProgramEventLocked(long now) {
+        if (mLastInstallEvent == null) {
+            return;
+        }
+        ApfProgramEvent ev = mLastInstallEvent;
+        mLastInstallEvent = null;
+        ev.actualLifetime = now - mLastTimeInstalledProgram;
+        if (ev.actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) {
+            return;
+        }
+        mMetricsLog.log(ev);
     }
 
     /**
@@ -1078,11 +1102,15 @@
         mRas.clear();
     }
 
-    public synchronized void setMulticastFilter(boolean enabled) {
-        if (mMulticastFilter != enabled) {
-            mMulticastFilter = enabled;
-            installNewProgramLocked();
+    public synchronized void setMulticastFilter(boolean isEnabled) {
+        if (mMulticastFilter == isEnabled) {
+            return;
         }
+        mMulticastFilter = isEnabled;
+        if (!isEnabled) {
+            mNumProgramUpdatesAllowingMulticast++;
+        }
+        installNewProgramLocked();
     }
 
     /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 76b1c90..a3b0429 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -631,6 +631,13 @@
 
         pw.println();
         pw.println(mTag + " connectivity packet log:");
+        pw.println();
+        pw.println("Debug with python and scapy via:");
+        pw.println("shell$ python");
+        pw.println(">>> from scapy import all as scapy");
+        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+        pw.println();
+
         pw.increaseIndent();
         mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
         pw.decreaseIndent();
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index ebd4b01..169cf4d 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -105,7 +105,7 @@
         invalidateMounts();
 
         mHandler = new H(IoThread.get().getLooper());
-        mHandler.sendEmptyMessageDelayed(H.MSG_LOAD_CACHED_QUOTAS_FROM_FILE, DELAY_IN_MILLIS);
+        mHandler.sendEmptyMessage(H.MSG_LOAD_CACHED_QUOTAS_FROM_FILE);
 
         mStorage.registerListener(new StorageEventListener() {
             @Override
@@ -137,7 +137,8 @@
                         android.Manifest.permission.PACKAGE_USAGE_STATS, TAG);
                 return;
             default:
-                throw new SecurityException("Blocked by mode " + mode);
+                throw new SecurityException("Package " + callingPackage + " from UID " + callingUid
+                        + " blocked by mode " + mode);
         }
     }
 
@@ -169,16 +170,21 @@
         enforcePermission(Binder.getCallingUid(), callingPackage);
 
         long cacheBytes = 0;
-        for (UserInfo user : mUser.getUsers()) {
-            final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null);
-            cacheBytes += stats.cacheBytes;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            for (UserInfo user : mUser.getUsers()) {
+                final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null);
+                cacheBytes += stats.cacheBytes;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
 
         if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
-            return Environment.getDataDirectory().getFreeSpace() + cacheBytes;
+            return Environment.getDataDirectory().getUsableSpace() + cacheBytes;
         } else {
             final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
-            return vol.getPath().getFreeSpace() + cacheBytes;
+            return vol.getPath().getUsableSpace() + cacheBytes;
         }
     }
 
@@ -208,7 +214,8 @@
 
         final ApplicationInfo appInfo;
         try {
-            appInfo = mPackage.getApplicationInfoAsUser(packageName, 0, userId);
+            appInfo = mPackage.getApplicationInfoAsUser(packageName,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
         } catch (NameNotFoundException e) {
             throw new IllegalStateException(e);
         }
@@ -251,8 +258,8 @@
 
         for (int i = 0; i < packageNames.length; i++) {
             try {
-                codePaths[i] = mPackage.getApplicationInfoAsUser(packageNames[i], 0,
-                        userId).getCodePath();
+                codePaths[i] = mPackage.getApplicationInfoAsUser(packageNames[i],
+                        PackageManager.MATCH_UNINSTALLED_PACKAGES, userId).getCodePath();
             } catch (NameNotFoundException e) {
                 throw new IllegalStateException(e);
             }
@@ -284,7 +291,8 @@
         }
 
         int[] appIds = null;
-        for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser(0, userId)) {
+        for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser(
+                PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)) {
             final int appId = UserHandle.getAppId(app.uid);
             if (!ArrayUtils.contains(appIds, appId)) {
                 appIds = ArrayUtils.appendInt(appIds, appId);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 632a1d6..748e32a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1312,6 +1312,68 @@
     public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY =
             "call_forwarding_blocks_while_roaming_string_array";
 
+    /**
+     * The day of the month (1-31) on which the data cycle rolls over.
+     * <p>
+     * If the current month does not have this day, the cycle will roll over at the start of the
+     * next month.
+     * <p>
+     * This setting may be still overridden by explicit user choice. By default, the platform value
+     * will be used.
+     */
+    public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT =
+            "monthly_data_cycle_day_int";
+
+    /**
+     * When {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG},
+     * or {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} are set to this value, the platform default
+     * value will be used for that key.
+     *
+     * @hide
+     */
+    public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1;
+
+    /**
+     * Flag indicating that a data cycle threshold should be disabled.
+     * <p>
+     * If {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} is set to this value, the platform's
+     * default data warning, if one exists, will be disabled. A user selected data warning will not
+     * be overridden.
+     * <p>
+     * If {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG} is set to this value, the platform's
+     * default data limit, if one exists, will be disabled. A user selected data limit will not be
+     * overridden.
+     */
+    public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2;
+
+    /**
+     * Controls the data usage warning.
+     * <p>
+     * If the user uses more than this amount of data in their billing cycle, as defined by
+     * {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, the user will be alerted about the usage.
+     * If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data usage warning will
+     * be disabled.
+     * <p>
+     * This setting may be overridden by explicit user choice. By default, the platform value
+     * will be used.
+     */
+    public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG =
+            "data_warning_threshold_bytes_long";
+
+    /**
+     * Controls the cellular data limit.
+     * <p>
+     * If the user uses more than this amount of data in their billing cycle, as defined by
+     * {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, cellular data will be turned off by the user's
+     * phone. If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data limit will be
+     * disabled.
+     * <p>
+     * This setting may be overridden by explicit user choice. By default, the platform value
+     * will be used.
+     */
+    public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG =
+            "data_limit_threshold_bytes_long";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1515,6 +1577,10 @@
                 });
         sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null);
 
+        sDefaults.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, DATA_CYCLE_USE_PLATFORM_DEFAULT);
+        sDefaults.putLong(KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, DATA_CYCLE_USE_PLATFORM_DEFAULT);
+        sDefaults.putLong(KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, DATA_CYCLE_USE_PLATFORM_DEFAULT);
+
         // Rat families: {GPRS, EDGE}, {EVDO, EVDO_A, EVDO_B}, {UMTS, HSPA, HSDPA, HSUPA, HSPAP},
         // {LTE, LTE_CA}
         // Order is important - lowest precidence first
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index a3e11c8..199a12a 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1620,7 +1620,7 @@
     //
     // Australia: Short codes are six or eight digits in length, starting with the prefix "19"
     //            followed by an additional four or six digits and two.
-    // Czech Republic: Codes are seven digits in length for MO and five (not billed) or
+    // Czechia: Codes are seven digits in length for MO and five (not billed) or
     //            eight (billed) for MT direction
     //
     // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 11105d6..48861bd 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -304,6 +304,7 @@
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(ApfProgramEvent.class),
                 aLong(200),
+                aLong(18),
                 anInt(7),
                 anInt(9),
                 anInt(2048),
@@ -320,7 +321,7 @@
                 "  apf_program_event <",
                 "    current_ras: 9",
                 "    drop_multicast: true",
-                "    effective_lifetime: 0",
+                "    effective_lifetime: 18",
                 "    filtered_ras: 7",
                 "    has_ipv4_addr: true",
                 "    lifetime: 200",
@@ -343,6 +344,8 @@
                 anInt(1),
                 anInt(2),
                 anInt(4),
+                anInt(7),
+                anInt(3),
                 anInt(2048));
 
         String want = joinLines(
@@ -360,8 +363,8 @@
                 "    max_program_size: 2048",
                 "    parse_errors: 2",
                 "    program_updates: 4",
-                "    program_updates_all: 0",
-                "    program_updates_allowing_multicast: 0",
+                "    program_updates_all: 7",
+                "    program_updates_allowing_multicast: 3",
                 "    received_ras: 10",
                 "    zero_lifetime_ras: 1",
                 "  >",
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 1f7c5f4..58511b9 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -118,7 +118,7 @@
     @SmallTest
     public void testRateLimiting() {
         final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
-        final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0);
+        final ApfProgramEvent ev = new ApfProgramEvent();
         final long fakeTimestamp = 1;
 
         int attempt = 100; // More than burst quota, but less than buffer size.
@@ -142,13 +142,24 @@
         // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
         IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
 
+        ApfStats apfStats = new ApfStats();
+        apfStats.durationMs = 45000;
+        apfStats.receivedRas = 10;
+        apfStats.matchingRas = 2;
+        apfStats.droppedRas = 2;
+        apfStats.parseErrors = 2;
+        apfStats.zeroLifetimeRas = 1;
+        apfStats.programUpdates = 4;
+        apfStats.programUpdatesAll = 7;
+        apfStats.programUpdatesAllowingMulticast = 3;
+        apfStats.maxProgramSize = 2048;
         Parcelable[] events = {
             new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED),
             new DhcpClientEvent("wlan0", "SomeState", 192),
             new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
             new IpManagerEvent("wlan0", IpManagerEvent.PROVISIONING_OK, 5678),
             new ValidationProbeEvent(120, 40730, ValidationProbeEvent.PROBE_HTTP, 204),
-            new ApfStats(45000, 10, 2, 2, 1, 2, 4, 2048),
+            apfStats,
             new RaEvent(2000, 400, 300, -1, 1000, -1)
         };
 
@@ -240,8 +251,8 @@
                 "    max_program_size: 2048",
                 "    parse_errors: 2",
                 "    program_updates: 4",
-                "    program_updates_all: 0",
-                "    program_updates_allowing_multicast: 0",
+                "    program_updates_all: 7",
+                "    program_updates_allowing_multicast: 3",
                 "    received_ras: 10",
                 "    zero_lifetime_ras: 1",
                 "  >",
diff --git a/tests/testables/Android.mk b/tests/testables/Android.mk
new file mode 100644
index 0000000..58399fd
--- /dev/null
+++ b/tests/testables/Android.mk
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := testables
+LOCAL_MODULE_TAG := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    mockito-updated-target-minus-junit4 \
+    legacy-android-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUIRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/SysUIRunner.java
rename to tests/testables/src/android/testing/AndroidTestingRunner.java
index fd99d1d..816ed03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysUIRunner.java
+++ b/tests/testables/src/android/testing/AndroidTestingRunner.java
@@ -12,14 +12,14 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui;
+package android.testing;
 
 import android.support.test.internal.runner.junit4.statement.RunAfters;
 import android.support.test.internal.runner.junit4.statement.RunBefores;
 import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
 
-import com.android.systemui.utils.TestableLooper.LooperStatement;
-import com.android.systemui.utils.TestableLooper.RunWithLooper;
+import android.testing.TestableLooper.LooperStatement;
+import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.After;
 import org.junit.Before;
@@ -32,12 +32,15 @@
 
 import java.util.List;
 
-public class SysUIRunner extends BlockJUnit4ClassRunner {
+/**
+ * A runner with support for extra annotations provided by the Testables library.
+ */
+public class AndroidTestingRunner extends BlockJUnit4ClassRunner {
 
     private final long mTimeout;
     private final Class<?> mKlass;
 
-    public SysUIRunner(Class<?> klass) throws InitializationError {
+    public AndroidTestingRunner(Class<?> klass) throws InitializationError {
         super(klass);
         mKlass = klass;
         // Can't seem to get reference to timeout parameter from here, so set default to 10 mins.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java b/tests/testables/src/android/testing/BaseFragmentTest.java
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
rename to tests/testables/src/android/testing/BaseFragmentTest.java
index 1678d92..53841d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
+++ b/tests/testables/src/android/testing/BaseFragmentTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
@@ -12,7 +12,9 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui;
+package android.testing;
+
+import static org.junit.Assert.assertNotNull;
 
 import android.annotation.Nullable;
 import android.app.Fragment;
@@ -21,20 +23,17 @@
 import android.app.FragmentManagerNonConfig;
 import android.graphics.PixelFormat;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Parcelable;
+import android.support.test.InstrumentationRegistry;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.widget.FrameLayout;
 
-import com.android.systemui.utils.TestableLooper;
-import com.android.systemui.utils.ViewUtils;
-import com.android.systemui.utils.leaks.LeakCheckedTest;
-
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.io.FileDescriptor;
@@ -46,7 +45,7 @@
  * the host for subclasses, so they can push it into desired states and do any unit testing
  * required.
  */
-public abstract class FragmentTestCase extends LeakCheckedTest {
+public abstract class BaseFragmentTest {
 
     private static final int VIEW_ID = 42;
     private final Class<? extends Fragment> mCls;
@@ -55,7 +54,10 @@
     protected FragmentController mFragments;
     protected Fragment mFragment;
 
-    public FragmentTestCase(Class<? extends Fragment> cls) {
+    @Rule
+    public final TestableContext mContext = getContext();
+
+    public BaseFragmentTest(Class<? extends Fragment> cls) {
         mCls = cls;
     }
 
@@ -64,6 +66,8 @@
         mView = new FrameLayout(mContext);
         mView.setId(VIEW_ID);
 
+        assertNotNull("BaseFragmentTest must be tagged with @RunWithLooper",
+                TestableLooper.get(this));
         TestableLooper.get(this).runWithLooper(() -> {
             mHandler = new Handler();
 
@@ -76,8 +80,8 @@
         });
     }
 
-    private String hex(Looper looper) {
-        return Integer.toHexString(System.identityHashCode(looper));
+    protected TestableContext getContext() {
+        return new TestableContext(InstrumentationRegistry.getContext());
     }
 
     @After
@@ -174,14 +178,14 @@
         return mView.findViewById(id);
     }
 
-    private class HostCallbacks extends FragmentHostCallback<FragmentTestCase> {
+    private class HostCallbacks extends FragmentHostCallback<BaseFragmentTest> {
         public HostCallbacks() {
-            super(mContext, FragmentTestCase.this.mHandler, 0);
+            super(mContext, BaseFragmentTest.this.mHandler, 0);
         }
 
         @Override
-        public FragmentTestCase onGetHost() {
-            return FragmentTestCase.this;
+        public BaseFragmentTest onGetHost() {
+            return BaseFragmentTest.this;
         }
 
         @Override
@@ -220,7 +224,7 @@
         @Nullable
         @Override
         public View onFindViewById(int id) {
-            return FragmentTestCase.this.findViewById(id);
+            return BaseFragmentTest.this.findViewById(id);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/tests/testables/src/android/testing/LayoutInflaterBuilder.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
rename to tests/testables/src/android/testing/LayoutInflaterBuilder.java
index 5cfe677..098302e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
+++ b/tests/testables/src/android/testing/LayoutInflaterBuilder.java
@@ -1,20 +1,18 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
+ * 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.
+ * 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.util;
+package android.testing;
 
 import android.annotation.NonNull;
 import android.content.Context;
diff --git a/tests/testables/src/android/testing/LeakCheck.java b/tests/testables/src/android/testing/LeakCheck.java
new file mode 100644
index 0000000..8daaa8f
--- /dev/null
+++ b/tests/testables/src/android/testing/LeakCheck.java
@@ -0,0 +1,101 @@
+/*
+ * 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.testing;
+
+import android.util.ArrayMap;
+import android.util.Log;
+
+import org.junit.Assert;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LeakCheck extends TestWatcher {
+
+    private final Map<String, Tracker> mTrackers = new HashMap<>();
+
+    public LeakCheck() {
+    }
+
+    @Override
+    protected void succeeded(Description description) {
+        verify();
+    }
+
+    public Tracker getTracker(String tag) {
+        Tracker t = mTrackers.get(tag);
+        if (t == null) {
+            t = new Tracker();
+            mTrackers.put(tag, t);
+        }
+        return t;
+    }
+
+    public void verify() {
+        mTrackers.values().forEach(Tracker::verify);
+    }
+
+    public static class LeakInfo {
+        private static final String TAG = "LeakInfo";
+        private List<Throwable> mThrowables = new ArrayList<>();
+
+        LeakInfo() {
+        }
+
+        public void addAllocation(Throwable t) {
+            // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
+            mThrowables.add(t);
+        }
+
+        public void clearAllocations() {
+            mThrowables.clear();
+        }
+
+        void verify() {
+            if (mThrowables.size() == 0) return;
+            Log.e(TAG, "Listener or binding not properly released");
+            for (Throwable t : mThrowables) {
+                Log.e(TAG, "Allocation found", t);
+            }
+            StringWriter writer = new StringWriter();
+            mThrowables.get(0).printStackTrace(new PrintWriter(writer));
+            Assert.fail("Listener or binding not properly released\n"
+                    + writer.toString());
+        }
+    }
+
+    public static class Tracker {
+        private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
+
+        public LeakInfo getLeakInfo(Object object) {
+            LeakInfo leakInfo = mObjects.get(object);
+            if (leakInfo == null) {
+                leakInfo = new LeakInfo();
+                mObjects.put(object, leakInfo);
+            }
+            return leakInfo;
+        }
+
+        void verify() {
+            mObjects.values().forEach(LeakInfo::verify);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java b/tests/testables/src/android/testing/TestableContentResolver.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java
rename to tests/testables/src/android/testing/TestableContentResolver.java
index 34f2e01..bfafbe0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java
+++ b/tests/testables/src/android/testing/TestableContentResolver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.utils;
+package android.testing;
 
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -29,14 +29,14 @@
 /**
  * Alternative to a MockContentResolver that falls back to real providers.
  */
-public class FakeContentResolver extends ContentResolver {
+public class TestableContentResolver extends ContentResolver {
 
     private final Map<String, ContentProvider> mProviders = Maps.newHashMap();
     private final ContentResolver mParent;
     private final ArraySet<ContentProvider> mInUse = new ArraySet<>();
     private boolean mFallbackToExisting;
 
-    public FakeContentResolver(Context context) {
+    public TestableContentResolver(Context context) {
         super(context);
         mParent = context.getContentResolver();
         mFallbackToExisting = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
similarity index 70%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
rename to tests/testables/src/android/testing/TestableContext.java
index 1429390..cb5d4cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.utils;
+package android.testing;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks;
@@ -32,36 +32,59 @@
 import android.util.ArrayMap;
 import android.view.LayoutInflater;
 
-import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.utils.leaks.Tracker;
+import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
 
-public class TestableContext extends ContextWrapper implements SysUiServiceProvider {
+/**
+ * A ContextWrapper with utilities specifically designed to make Testing easier.
+ *
+ * <ul>
+ * <li>System services can be mocked out with {@link #addMockSystemService}</li>
+ * <li>Service binding can be mocked out with {@link #addMockService}</li>
+ * <li>Settings support {@link TestableSettings}</li>
+ * <li>Has support for {@link LeakCheck} for services and receivers</li>
+ * </ul>
+ *
+ * <p>TestableContext should be defined as a rule on your test so it can clean up after itself.
+ * Like the following:</p>
+ * <pre class="prettyprint">
+ * {@literal
+ * @Rule
+ * private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext());
+ * }
+ * </pre>
+ */
+public class TestableContext extends ContextWrapper implements TestRule {
 
-    private final FakeContentResolver mFakeContentResolver;
-    private final FakeSettingsProvider mSettingsProvider;
+    private final TestableContentResolver mTestableContentResolver;
+    private final TestableSettings mSettingsProvider;
 
     private ArrayMap<String, Object> mMockSystemServices;
     private ArrayMap<ComponentName, IBinder> mMockServices;
     private ArrayMap<ServiceConnection, ComponentName> mActiveServices;
-    private ArrayMap<Class<?>, Object> mComponents;
 
     private PackageManager mMockPackageManager;
-    private Tracker mReceiver;
-    private Tracker mService;
-    private Tracker mComponent;
+    private LeakCheck.Tracker mReceiver;
+    private LeakCheck.Tracker mService;
+    private LeakCheck.Tracker mComponent;
 
-    public TestableContext(Context base, SysuiTestCase test) {
+    public TestableContext(Context base) {
+        this(base, null);
+    }
+
+    public TestableContext(Context base, LeakCheck check) {
         super(base);
-        mFakeContentResolver = new FakeContentResolver(base);
+        mTestableContentResolver = new TestableContentResolver(base);
         ContentProviderClient settings = base.getContentResolver()
                 .acquireContentProviderClient(Settings.AUTHORITY);
-        mSettingsProvider = FakeSettingsProvider.getFakeSettingsProvider(settings,
-                mFakeContentResolver);
-        mFakeContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
-        mReceiver = test.getTracker("receiver");
-        mService = test.getTracker("service");
-        mComponent = test.getTracker("component");
+        mSettingsProvider = TestableSettings.getFakeSettingsProvider(settings,
+                mTestableContentResolver);
+        mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider.getProvider());
+        mReceiver = check != null ? check.getTracker("receiver") : null;
+        mService = check != null ? check.getTracker("service") : null;
+        mComponent = check != null ? check.getTracker("component") : null;
     }
 
     public void setMockPackageManager(PackageManager mock) {
@@ -86,19 +109,15 @@
     }
 
     public void addMockSystemService(String name, Object service) {
-        mMockSystemServices = lazyInit(mMockSystemServices);
+        if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>();
         mMockSystemServices.put(name, service);
     }
 
     public void addMockService(ComponentName component, IBinder service) {
-        mMockServices = lazyInit(mMockServices);
+        if (mMockServices == null) mMockServices = new ArrayMap<>();
         mMockServices.put(component, service);
     }
 
-    private <T, V> ArrayMap<T, V> lazyInit(ArrayMap<T, V> services) {
-        return services != null ? services : new ArrayMap<T, V>();
-    }
-
     @Override
     public Object getSystemService(String name) {
         if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) {
@@ -110,13 +129,13 @@
         return super.getSystemService(name);
     }
 
-    public FakeSettingsProvider getSettingsProvider() {
+    public TestableSettings getSettingsProvider() {
         return mSettingsProvider;
     }
 
     @Override
-    public FakeContentResolver getContentResolver() {
-        return mFakeContentResolver;
+    public TestableContentResolver getContentResolver() {
+        return mTestableContentResolver;
     }
 
     @Override
@@ -177,7 +196,7 @@
 
     private boolean checkMocks(ComponentName component, ServiceConnection conn) {
         if (mMockServices != null && component != null && mMockServices.containsKey(component)) {
-            mActiveServices = lazyInit(mActiveServices);
+            if (mActiveServices == null) mActiveServices = new ArrayMap<>();
             mActiveServices.put(conn, component);
             conn.onServiceConnected(component, mMockServices.get(component));
             return true;
@@ -212,13 +231,18 @@
         super.unregisterComponentCallbacks(callback);
     }
 
-    @SuppressWarnings("unchecked")
-    public <T> T getComponent(Class<T> interfaceType) {
-        return (T) (mComponents != null ? mComponents.get(interfaceType) : null);
-    }
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new TestWatcher() {
+            @Override
+            protected void succeeded(Description description) {
+                mSettingsProvider.clearOverrides();
+            }
 
-    public <T, C extends T> void putComponent(Class<T> interfaceType, C component) {
-        mComponents = lazyInit(mComponents);
-        mComponents.put(interfaceType, component);
+            @Override
+            protected void failed(Throwable e, Description description) {
+                mSettingsProvider.clearOverrides();
+            }
+        }.apply(base, description);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java b/tests/testables/src/android/testing/TestableImageView.java
similarity index 92%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java
rename to tests/testables/src/android/testing/TestableImageView.java
index b131460..901e25b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java
+++ b/tests/testables/src/android/testing/TestableImageView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.utils;
+package android.testing;
 
 import android.annotation.DrawableRes;
 import android.annotation.Nullable;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java
rename to tests/testables/src/android/testing/TestableLooper.java
index 8902e0c..8a33cf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.utils;
+package android.testing;
 
 import android.os.Handler;
 import android.os.Looper;
diff --git a/tests/testables/src/android/testing/TestableSettings.java b/tests/testables/src/android/testing/TestableSettings.java
new file mode 100644
index 0000000..d19f1ef
--- /dev/null
+++ b/tests/testables/src/android/testing/TestableSettings.java
@@ -0,0 +1,318 @@
+/*
+ * 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.testing;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
+import android.test.mock.MockContentProvider;
+import android.testing.TestableSettings.SettingOverrider.Builder;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Allows calls to android.provider.Settings to be tested easier.  A SettingOverride
+ * can be acquired and a set of specific settings can be set to a value (and not changed
+ * in the system when set), so that they can be tested without breaking the test device.
+ * <p>
+ * To use, in the before method acquire the override add all settings that will affect if
+ * your test passes or not.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * mSettingOverride = mTestableContext.getSettingsProvider().acquireOverridesBuilder()
+ * .addSetting("secure", Secure.USER_SETUP_COMPLETE, "0")
+ * .build();
+ * }
+ * </pre>
+ *
+ * Then in the after free up the settings.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * mSettingOverride.release();
+ * }
+ * </pre>
+ */
+public class TestableSettings {
+
+    private static final String TAG = "TestableSettings";
+    private static final boolean DEBUG = false;
+
+    // Number of times to try to acquire a setting if in use.
+    private static final int MAX_TRIES = 10;
+    // Time to wait for each setting.  WAIT_TIMEOUT * MAX_TRIES will be the maximum wait time
+    // for a setting.
+    private static final long WAIT_TIMEOUT = 1000;
+
+    private static TestableSettingsProvider sInstance;
+
+    private final TestableSettingsProvider mProvider;
+
+    private TestableSettings(TestableSettingsProvider provider) {
+        mProvider = provider;
+    }
+
+    public Builder acquireOverridesBuilder() {
+        return new Builder(this);
+    }
+
+    public void clearOverrides() {
+        List<SettingOverrider> overrides = mProvider.mOwners.remove(this);
+        if (overrides != null) {
+            overrides.forEach(override -> override.ensureReleased());
+        }
+    }
+
+    private void acquireSettings(SettingOverrider overridder, Set<String> keys)
+            throws AcquireTimeoutException {
+        mProvider.acquireSettings(overridder, keys, this);
+    }
+
+    ContentProvider getProvider() {
+        return mProvider;
+    }
+
+    @VisibleForTesting
+    Object getLock() {
+        return mProvider.mOverrideMap;
+    }
+
+    public static class SettingOverrider {
+        private final Set<String> mValidKeys;
+        private final Map<String, String> mValueMap = new ArrayMap<>();
+        private final TestableSettings mSettings;
+        private boolean mReleased;
+        public Throwable mObtain;
+
+        private SettingOverrider(Set<String> keys, TestableSettings provider) {
+            mValidKeys = new ArraySet<>(keys);
+            mSettings = provider;
+        }
+
+        private void ensureReleased() {
+            if (!mReleased) {
+                release();
+            }
+        }
+
+        public void release() {
+            mSettings.mProvider.releaseSettings(mValidKeys);
+            mReleased = true;
+        }
+
+        private void putDirect(String key, String value) {
+            mValueMap.put(key, value);
+        }
+
+        public void put(String table, String key, String value) {
+            if (!mValidKeys.contains(key(table, key))) {
+                throw new IllegalArgumentException("Key " + table + " " + key
+                        + " not acquired for this overrider");
+            }
+            mValueMap.put(key(table, key), value);
+        }
+
+        public void remove(String table, String key) {
+            if (!mValidKeys.contains(key(table, key))) {
+                throw new IllegalArgumentException("Key " + table + " " + key
+                        + " not acquired for this overrider");
+            }
+            mValueMap.remove(key(table, key));
+        }
+
+        public String get(String table, String key) {
+            if (!mValidKeys.contains(key(table, key))) {
+                throw new IllegalArgumentException("Key " + table + " " + key
+                        + " not acquired for this overrider");
+            }
+            Log.d(TAG, "Get " + table + " " + key + " " + mValueMap.get(key(table, key)));
+            return mValueMap.get(key(table, key));
+        }
+
+        public static class Builder {
+            private final TestableSettings mProvider;
+            private Set<String> mKeys = new ArraySet<>();
+            private Map<String, String> mValues = new ArrayMap<>();
+
+            private Builder(TestableSettings provider) {
+                mProvider = provider;
+            }
+
+            public Builder addSetting(String table, String key) {
+                mKeys.add(key(table, key));
+                return this;
+            }
+
+            public Builder addSetting(String table, String key, String value) {
+                addSetting(table, key);
+                mValues.put(key(table, key), value);
+                return this;
+            }
+
+            public SettingOverrider build() throws AcquireTimeoutException {
+                SettingOverrider overrider = new SettingOverrider(mKeys, mProvider);
+                mProvider.acquireSettings(overrider, mKeys);
+                mValues.forEach((key, value) -> overrider.putDirect(key, value));
+                return overrider;
+            }
+        }
+    }
+
+    private static class TestableSettingsProvider extends MockContentProvider {
+
+        private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>();
+        private final Map<Object, List<SettingOverrider>> mOwners = new ArrayMap<>();
+
+        private final ContentProviderClient mSettings;
+        private final ContentResolver mResolver;
+
+        public TestableSettingsProvider(ContentProviderClient settings, ContentResolver resolver) {
+            mSettings = settings;
+            mResolver = resolver;
+        }
+
+        private void releaseSettings(Set<String> keys) {
+            synchronized (mOverrideMap) {
+                for (String key : keys) {
+                    if (DEBUG) Log.d(TAG, "Releasing " + key);
+                    mOverrideMap.remove(key);
+                }
+                if (DEBUG) Log.d(TAG, "Notifying");
+                mOverrideMap.notify();
+            }
+        }
+
+        private boolean checkKeysLocked(Set<String> keys, boolean shouldThrow)
+                throws AcquireTimeoutException {
+            for (String key : keys) {
+                if (mOverrideMap.containsKey(key)) {
+                    if (shouldThrow) {
+                        if (DEBUG) Log.e(TAG, "Lock obtained at",
+                                mOverrideMap.get(key).mObtain);
+                        throw new AcquireTimeoutException("Could not acquire " + key,
+                                mOverrideMap.get(key).mObtain);
+                    }
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private void acquireSettings(SettingOverrider overridder, Set<String> keys,
+                Object owner) throws AcquireTimeoutException {
+            synchronized (mOwners) {
+                List<SettingOverrider> list = mOwners.get(owner);
+                if (list == null) {
+                    list = new ArrayList<>();
+                    mOwners.put(owner, list);
+                }
+                list.add(overridder);
+            }
+            synchronized (mOverrideMap) {
+                for (int i = 0; i < MAX_TRIES; i++) {
+                    if (checkKeysLocked(keys, false)) break;
+                    try {
+                        if (DEBUG) Log.d(TAG, "Waiting for contention to finish");
+                        mOverrideMap.wait(WAIT_TIMEOUT);
+                    } catch (InterruptedException e) {
+                    }
+                }
+                overridder.mObtain = new Throwable();
+                checkKeysLocked(keys, true);
+                for (String key : keys) {
+                    if (DEBUG) Log.d(TAG, "Acquiring " + key);
+                    mOverrideMap.put(key, overridder);
+                }
+            }
+        }
+
+        public Bundle call(String method, String arg, Bundle extras) {
+            // Methods are "GET_system", "GET_global", "PUT_secure", etc.
+            final String[] commands = method.split("_", 2);
+            final String op = commands[0];
+            final String table = commands[1];
+
+            synchronized (mOverrideMap) {
+                SettingOverrider overrider = mOverrideMap.get(key(table, arg));
+                if (overrider == null) {
+                    // Fall through to real settings.
+                    try {
+                        if (DEBUG) Log.d(TAG, "Falling through to real settings " + method);
+                        // TODO: Add our own version of caching to handle this.
+                        Bundle call = mSettings.call(method, arg, extras);
+                        call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY);
+                        return call;
+                    } catch (RemoteException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                String value;
+                Bundle out = new Bundle();
+                switch (op) {
+                    case "GET":
+                        value = overrider.get(table, arg);
+                        if (value != null) {
+                            out.putString(Settings.NameValueTable.VALUE, value);
+                        }
+                        break;
+                    case "PUT":
+                        value = extras.getString(Settings.NameValueTable.VALUE, null);
+                        if (value != null) {
+                            overrider.put(table, arg, value);
+                        } else {
+                            overrider.remove(table, arg);
+                        }
+                        break;
+                    default:
+                        throw new UnsupportedOperationException("Unknown command " + method);
+                }
+                return out;
+            }
+        }
+    }
+
+    public static class AcquireTimeoutException extends Exception {
+        public AcquireTimeoutException(String str, Throwable cause) {
+            super(str, cause);
+        }
+    }
+
+    private static String key(String table, String key) {
+        return table + "_" + key;
+    }
+
+    /**
+     * Since the settings provider is cached inside android.provider.Settings, this must
+     * be gotten statically to ensure there is only one instance referenced.
+     */
+    public static TestableSettings getFakeSettingsProvider(ContentProviderClient settings,
+            ContentResolver resolver) {
+        if (sInstance == null) {
+            sInstance = new TestableSettingsProvider(settings, resolver);
+        }
+        return new TestableSettings(sInstance);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/UiThreadTest.java b/tests/testables/src/android/testing/UiThreadTest.java
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/UiThreadTest.java
rename to tests/testables/src/android/testing/UiThreadTest.java
index 58369b1..e40e1d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/UiThreadTest.java
+++ b/tests/testables/src/android/testing/UiThreadTest.java
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui;
+package android.testing;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java b/tests/testables/src/android/testing/ViewUtils.java
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java
rename to tests/testables/src/android/testing/ViewUtils.java
index 678b9f4..5a651aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java
+++ b/tests/testables/src/android/testing/ViewUtils.java
@@ -12,19 +12,14 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.utils;
+package android.testing;
 
 import android.content.pm.ApplicationInfo;
 import android.graphics.PixelFormat;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
+import android.support.test.InstrumentationRegistry;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
-import android.support.test.InstrumentationRegistry;
-
-import com.android.systemui.SysuiTestCase;
 
 public class ViewUtils {
 
diff --git a/tests/testables/tests/Android.mk b/tests/testables/tests/Android.mk
new file mode 100644
index 0000000..752d536
--- /dev/null
+++ b/tests/testables/tests/Android.mk
@@ -0,0 +1,39 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_PACKAGE_NAME := TestablesTest
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-Iaidl-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    mockito-updated-target-minus-junit4 \
+    legacy-android-test \
+	testables
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f6006b0
--- /dev/null
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.testables">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.testables"
+        android:label="Tests for Testables">
+    </instrumentation>
+</manifest>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java
rename to tests/testables/tests/src/android/testing/TestableLooperTest.java
index 2416e1d..18e5fff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.utils;
+package android.testing;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -27,20 +27,17 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-
-import com.android.systemui.SysUIRunner;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.utils.TestableLooper.MessageHandler;
-import com.android.systemui.utils.TestableLooper.RunWithLooper;
+import android.testing.TestableLooper.MessageHandler;
+import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
 @RunWithLooper
-public class TestableLooperTest extends SysuiTestCase {
+public class TestableLooperTest {
 
     private TestableLooper mTestableLooper;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java b/tests/testables/tests/src/android/testing/TestableSettingsTest.java
similarity index 88%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java
rename to tests/testables/tests/src/android/testing/TestableSettingsTest.java
index 63bb5e7..1b01542 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java
+++ b/tests/testables/tests/src/android/testing/TestableSettingsTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
@@ -12,7 +12,7 @@
  * permissions and limitations under the License.
  */
 
-package com.android.systemui.utils;
+package android.testing;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -25,29 +25,32 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+import android.testing.TestableSettings.AcquireTimeoutException;
+import android.testing.TestableSettings.SettingOverrider;
 import android.util.Log;
 
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.utils.FakeSettingsProvider.AcquireTimeoutException;
-import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider;
-
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
-public class FakeSettingsProviderTest extends SysuiTestCase {
+public class TestableSettingsTest {
 
     public static final String NONEXISTENT_SETTING = "nonexistent_setting";
-    private static final String TAG = "FakeSettingsProviderTest";
+    private static final String TAG = "TestableSettingsTest";
     private SettingOverrider mOverrider;
     private ContentResolver mContentResolver;
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getContext());
 
     @Before
     public void setup() throws AcquireTimeoutException {
-        mOverrider = mContext.getSettingsProvider().acquireOverridesBuilder(this)
+        mOverrider = mContext.getSettingsProvider().acquireOverridesBuilder()
                 .addSetting("secure", NONEXISTENT_SETTING)
                 .addSetting("global", NONEXISTENT_SETTING, "initial value")
                 .addSetting("global", Global.DEVICE_PROVISIONED)
@@ -55,13 +58,6 @@
         mContentResolver = mContext.getContentResolver();
     }
 
-    @After
-    public void teardown() {
-        if (mOverrider != null) {
-            mOverrider.release();
-        }
-    }
-
     @Test
     public void testInitialValueSecure() {
         String value = Secure.getString(mContentResolver, NONEXISTENT_SETTING);
@@ -109,8 +105,9 @@
 
     @Test
     public void testAutoRelease() throws Exception {
-        super.cleanup();
-        mContext.getSettingsProvider().acquireOverridesBuilder(this)
+        mOverrider.release();
+        mOverrider = null;
+        mContext.getSettingsProvider().acquireOverridesBuilder()
                 .addSetting("global", Global.DEVICE_PROVISIONED)
                 .build();
     }
@@ -122,7 +119,7 @@
         String secure = "secure";
         String key = "something shared";
         String[] result = new String[1];
-        overriders[0] = mContext.getSettingsProvider().acquireOverridesBuilder(this)
+        overriders[0] = mContext.getSettingsProvider().acquireOverridesBuilder()
                 .addSetting(secure, key, "Some craziness")
                 .build();
         synchronized (lock) {
@@ -137,7 +134,7 @@
                             lock.notify();
                         }
                         overriders[1] = mContext.getSettingsProvider()
-                                .acquireOverridesBuilder(FakeSettingsProviderTest.this)
+                                .acquireOverridesBuilder()
                                 .addSetting(secure, key, "default value")
                                 .build();
                         // Ensure that the default is the one we set, and not left over from
diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/AppOne/Android.mk
index a6f32d4..38bd5b5 100644
--- a/tools/aapt2/integration-tests/AppOne/Android.mk
+++ b/tools/aapt2/integration-tests/AppOne/Android.mk
@@ -21,6 +21,7 @@
 LOCAL_PACKAGE_NAME := AaptTestAppOne
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets $(LOCAL_PATH)/assets2
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     AaptTestStaticLibOne \
     AaptTestStaticLibTwo
diff --git a/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt b/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt
new file mode 100644
index 0000000..1251949
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt
@@ -0,0 +1 @@
+subdir/subsubdir/test.txt comes from assets
diff --git a/tools/aapt2/integration-tests/AppOne/assets/test.txt b/tools/aapt2/integration-tests/AppOne/assets/test.txt
new file mode 100644
index 0000000..88266de
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/assets/test.txt
@@ -0,0 +1 @@
+test.txt came from assets
diff --git a/tools/aapt2/integration-tests/AppOne/assets2/new.txt b/tools/aapt2/integration-tests/AppOne/assets2/new.txt
new file mode 100644
index 0000000..f4963a9
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/assets2/new.txt
@@ -0,0 +1 @@
+new.txt came from assets2
diff --git a/tools/aapt2/integration-tests/AppOne/assets2/test.txt b/tools/aapt2/integration-tests/AppOne/assets2/test.txt
new file mode 100644
index 0000000..5d8b36c
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/assets2/test.txt
@@ -0,0 +1 @@
+test.txt came from assets2
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index c8f0217..1042111 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -77,6 +77,7 @@
   std::string manifest_path;
   std::vector<std::string> include_paths;
   std::vector<std::string> overlay_files;
+  std::vector<std::string> assets_dirs;
   bool output_to_directory = false;
   bool auto_add_overlay = false;
 
@@ -1412,6 +1413,46 @@
     return doc;
   }
 
+  bool CopyAssetsDirsToApk(IArchiveWriter* writer) {
+    std::map<std::string, std::unique_ptr<io::RegularFile>> merged_assets;
+    for (const std::string& assets_dir : options_.assets_dirs) {
+      Maybe<std::vector<std::string>> files =
+          file::FindFiles(assets_dir, context_->GetDiagnostics(), nullptr);
+      if (!files) {
+        return false;
+      }
+
+      for (const std::string& file : files.value()) {
+        std::string full_key = "assets/" + file;
+        std::string full_path = assets_dir;
+        file::AppendPath(&full_path, file);
+
+        auto iter = merged_assets.find(full_key);
+        if (iter == merged_assets.end()) {
+          merged_assets.emplace(std::move(full_key),
+                                util::make_unique<io::RegularFile>(Source(std::move(full_path))));
+        } else if (context_->IsVerbose()) {
+          context_->GetDiagnostics()->Warn(DiagMessage(iter->second->GetSource())
+                                           << "asset file overrides '" << full_path << "'");
+        }
+      }
+    }
+
+    for (auto& entry : merged_assets) {
+      uint32_t compression_flags = ArchiveEntry::kCompress;
+      std::string extension = file::GetExtension(entry.first).to_string();
+      if (options_.extensions_to_not_compress.count(extension) > 0) {
+        compression_flags = 0u;
+      }
+
+      if (!CopyFileToArchive(entry.second.get(), entry.first, compression_flags, writer,
+                             context_)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   /**
    * Writes the AndroidManifest, ResourceTable, and all XML files referenced by
    * the ResourceTable to the IArchiveWriter.
@@ -1724,11 +1765,9 @@
     }
 
     // Start writing the base APK.
-    std::unique_ptr<IArchiveWriter> archive_writer =
-        MakeArchiveWriter(options_.output_path);
+    std::unique_ptr<IArchiveWriter> archive_writer = MakeArchiveWriter(options_.output_path);
     if (!archive_writer) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed to create archive");
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed to create archive");
       return 1;
     }
 
@@ -1743,16 +1782,15 @@
       XmlReferenceLinker manifest_linker;
       if (manifest_linker.Consume(context_, manifest_xml.get())) {
         if (options_.generate_proguard_rules_path &&
-            !proguard::CollectProguardRulesForManifest(
-                Source(options_.manifest_path), manifest_xml.get(),
-                &proguard_keep_set)) {
+            !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
+                                                       manifest_xml.get(), &proguard_keep_set)) {
           error = true;
         }
 
         if (options_.generate_main_dex_proguard_rules_path &&
-            !proguard::CollectProguardRulesForManifest(
-                Source(options_.manifest_path), manifest_xml.get(),
-                &proguard_main_dex_keep_set, true)) {
+            !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
+                                                       manifest_xml.get(),
+                                                       &proguard_main_dex_keep_set, true)) {
           error = true;
         }
 
@@ -1776,13 +1814,15 @@
     }
 
     if (error) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed processing manifest");
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed processing manifest");
       return 1;
     }
 
-    if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(),
-                  &final_table_)) {
+    if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) {
+      return 1;
+    }
+
+    if (!CopyAssetsDirsToApk(archive_writer.get())) {
       return 1;
     }
 
@@ -1863,12 +1903,6 @@
                            proguard_main_dex_keep_set)) {
       return 1;
     }
-
-    if (context_->IsVerbose()) {
-      DebugPrintTableOptions debug_print_table_options;
-      debug_print_table_options.show_sources = true;
-      Debug::PrintTable(&final_table_, debug_print_table_options);
-    }
     return 0;
   }
 
@@ -1916,6 +1950,9 @@
           .RequiredFlag("--manifest", "Path to the Android manifest to build",
                         &options.manifest_path)
           .OptionalFlagList("-I", "Adds an Android APK to link against", &options.include_paths)
+          .OptionalFlagList("-A",
+                            "An assets directory to include in the APK. These are unprocessed.",
+                            &options.assets_dirs)
           .OptionalFlagList("-R",
                             "Compilation unit to link, using `overlay` semantics.\n"
                             "The last conflicting resource given takes precedence.",
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index aa840e2..d10351b 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -264,5 +264,57 @@
   return true;
 }
 
+Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag,
+                                          const FileFilter* filter) {
+  const std::string root_dir = path.to_string();
+  std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
+  if (!d) {
+    diag->Error(DiagMessage() << android::base::SystemErrorCodeToString(errno));
+    return {};
+  }
+
+  std::vector<std::string> files;
+  std::vector<std::string> subdirs;
+  while (struct dirent* entry = readdir(d.get())) {
+    if (util::StartsWith(entry->d_name, ".")) {
+      continue;
+    }
+
+    std::string file_name = entry->d_name;
+    std::string full_path = root_dir;
+    AppendPath(&full_path, file_name);
+    const FileType file_type = GetFileType(full_path);
+
+    if (filter != nullptr) {
+      if (!(*filter)(file_name, file_type)) {
+        continue;
+      }
+    }
+
+    if (file_type == file::FileType::kDirectory) {
+      subdirs.push_back(std::move(file_name));
+    } else {
+      files.push_back(std::move(file_name));
+    }
+  }
+
+  // Now process subdirs.
+  for (const std::string& subdir : subdirs) {
+    std::string full_subdir = root_dir;
+    AppendPath(&full_subdir, subdir);
+    Maybe<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter);
+    if (!subfiles) {
+      return {};
+    }
+
+    for (const std::string& subfile : subfiles.value()) {
+      std::string new_file = subdir;
+      AppendPath(&new_file, subfile);
+      files.push_back(new_file);
+    }
+  }
+  return files;
+}
+
 }  // namespace file
 }  // namespace aapt
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index 95c492f..b3b1e48 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -132,6 +132,11 @@
   std::vector<std::string> pattern_tokens_;
 };
 
+// Returns a list of files relative to the directory identified by `path`.
+// An optional FileFilter filters out any files that don't pass.
+Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag,
+                                          const FileFilter* filter = nullptr);
+
 }  // namespace file
 }  // namespace aapt
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index bbe96a7..447cafb 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -971,10 +971,13 @@
     }
 
     /**
-     * Query for a Hotspot 2.0 release 2 OSU icon file.
+     * Query for a Hotspot 2.0 release 2 OSU icon file. An {@link #ACTION_PASSPOINT_ICON} intent
+     * will be broadcasted once the request is completed.  The return value of
+     * {@link IconInfo#getData} from the intent extra will indicate the result of the request.
+     * A value of {@code null} will indicate a failure.
      *
      * @param bssid The BSSID of the AP
-     * @param fileName File name of the icon to query
+     * @param fileName Name of the icon file (remote file) to query from the AP
      */
     public void queryPasspointIcon(long bssid, String fileName) {
         try {