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 {