Merge "Fix @see tag."
diff --git a/Android.mk b/Android.mk
index fbcea90..63ad83f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -464,6 +464,8 @@
telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl \
telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl \
telephony/java/com/android/ims/internal/IImsService.aidl \
+ telephony/java/com/android/ims/internal/IImsServiceController.aidl \
+ telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl \
telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
telephony/java/com/android/ims/internal/IImsUt.aidl \
telephony/java/com/android/ims/internal/IImsUtListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index 6730ab0..abc4de9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5050,6 +5050,7 @@
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+ field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
@@ -5215,6 +5216,7 @@
method public android.app.Notification.Builder setChannel(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
method public android.app.Notification.Builder setColor(int);
+ method public android.app.Notification.Builder setColorized(boolean);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
@@ -6123,6 +6125,7 @@
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+ method public android.content.Intent createAdminSupportIntent(java.lang.String);
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
method public void enableSystemApp(android.content.ComponentName, java.lang.String);
method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
@@ -6353,6 +6356,8 @@
field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+ field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+ field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -14915,6 +14920,7 @@
field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -14994,6 +15000,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_STATE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -34275,6 +34282,8 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
+ field public static final java.lang.String ARCHIVED = "archived";
+ field public static final java.lang.String BACKED_UP = "backed_up";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -34282,6 +34291,7 @@
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
+ field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicmail";
field public static final java.lang.String IS_READ = "is_read";
field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -34289,6 +34299,7 @@
field public static final java.lang.String NUMBER = "number";
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String RESTORED = "restored";
field public static final java.lang.String SOURCE_DATA = "source_data";
field public static final java.lang.String SOURCE_PACKAGE = "source_package";
field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -35666,17 +35677,25 @@
ctor public AutoFillService();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
+ method public void onDatasetAuthenticationRequest(android.os.Bundle, int);
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
+ field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
+ field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1
+ field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2
+ field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
public final class FillCallback {
+ method public void onDatasetAuthentication(android.view.autofill.Dataset, int);
method public void onFailure(java.lang.CharSequence);
+ method public void onFillResponseAuthentication(int);
method public void onSuccess(android.view.autofill.FillResponse);
}
@@ -38106,6 +38125,7 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
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_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";
@@ -38721,6 +38741,7 @@
method public int getSimState();
method public int getSimState(int);
method public java.lang.String getSubscriberId();
+ method public java.lang.String getVisualVoicemailPackageName(android.telecom.PhoneAccountHandle);
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
method public int getVoiceNetworkType();
@@ -38773,6 +38794,7 @@
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
+ field public static final java.lang.String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
@@ -38781,6 +38803,7 @@
field public static final java.lang.String EXTRA_STATE_OFFHOOK;
field public static final java.lang.String EXTRA_STATE_RINGING;
field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
+ field public static final java.lang.String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -46293,6 +46316,8 @@
public static final class Dataset.Builder {
ctor public Dataset.Builder(java.lang.CharSequence);
method public android.view.autofill.Dataset build();
+ method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
}
@@ -46308,6 +46333,8 @@
method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
method public android.view.autofill.FillResponse build();
+ method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index c4dd603..063362e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -37,6 +37,7 @@
field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
field public static final java.lang.String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
+ field public static final java.lang.String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
field public static final java.lang.String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
@@ -5210,6 +5211,7 @@
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+ field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
@@ -5377,6 +5379,7 @@
method public android.app.Notification.Builder setChannel(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
method public android.app.Notification.Builder setColor(int);
+ method public android.app.Notification.Builder setColorized(boolean);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
@@ -6319,6 +6322,7 @@
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+ method public android.content.Intent createAdminSupportIntent(java.lang.String);
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
method public void enableSystemApp(android.content.ComponentName, java.lang.String);
method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
@@ -6576,6 +6580,8 @@
field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+ field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+ field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -15481,6 +15487,7 @@
field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -15560,6 +15567,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_STATE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -26613,6 +26621,10 @@
field public static final android.os.Parcelable.Creator<android.net.Network> CREATOR;
}
+ public class NetworkBadging {
+ method public static android.graphics.drawable.Drawable getWifiIcon(int, int, android.content.res.Resources.Theme);
+ }
+
public final class NetworkCapabilities implements android.os.Parcelable {
ctor public NetworkCapabilities(android.net.NetworkCapabilities);
method public int describeContents();
@@ -37279,6 +37291,8 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
+ field public static final java.lang.String ARCHIVED = "archived";
+ field public static final java.lang.String BACKED_UP = "backed_up";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -37286,6 +37300,7 @@
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
+ field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicmail";
field public static final java.lang.String IS_READ = "is_read";
field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -37293,6 +37308,7 @@
field public static final java.lang.String NUMBER = "number";
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String RESTORED = "restored";
field public static final java.lang.String SOURCE_DATA = "source_data";
field public static final java.lang.String SOURCE_PACKAGE = "source_package";
field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -38682,17 +38698,25 @@
ctor public AutoFillService();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
+ method public void onDatasetAuthenticationRequest(android.os.Bundle, int);
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
+ field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
+ field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1
+ field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2
+ field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
public final class FillCallback {
+ method public void onDatasetAuthentication(android.view.autofill.Dataset, int);
method public void onFailure(java.lang.CharSequence);
+ method public void onFillResponseAuthentication(int);
method public void onSuccess(android.view.autofill.FillResponse);
}
@@ -41396,6 +41420,7 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
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_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";
@@ -42053,6 +42078,7 @@
method public int getSimState(int);
method public java.lang.String getSubscriberId();
method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
+ method public java.lang.String getVisualVoicemailPackageName(android.telecom.PhoneAccountHandle);
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
method public int getVoiceNetworkType();
@@ -42131,6 +42157,7 @@
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
+ field public static final java.lang.String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
@@ -42139,6 +42166,7 @@
field public static final java.lang.String EXTRA_STATE_OFFHOOK;
field public static final java.lang.String EXTRA_STATE_RINGING;
field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
+ field public static final java.lang.String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -42345,6 +42373,15 @@
}
+package android.telephony.ims {
+
+ public class ImsServiceBase extends android.app.Service {
+ ctor public ImsServiceBase();
+ method public android.os.IBinder onBind(android.content.Intent);
+ }
+
+}
+
package android.test {
public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
@@ -49678,6 +49715,8 @@
public static final class Dataset.Builder {
ctor public Dataset.Builder(java.lang.CharSequence);
method public android.view.autofill.Dataset build();
+ method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
}
@@ -49693,6 +49732,8 @@
method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
method public android.view.autofill.FillResponse build();
+ method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 68d86df..2f75020 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5060,6 +5060,7 @@
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+ field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
@@ -5225,6 +5226,7 @@
method public android.app.Notification.Builder setChannel(java.lang.String);
method public android.app.Notification.Builder setChronometerCountDown(boolean);
method public android.app.Notification.Builder setColor(int);
+ method public android.app.Notification.Builder setColorized(boolean);
method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
@@ -6140,6 +6142,7 @@
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+ method public android.content.Intent createAdminSupportIntent(java.lang.String);
method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
method public void enableSystemApp(android.content.ComponentName, java.lang.String);
method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
@@ -6348,6 +6351,7 @@
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE";
field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID";
+ field public static final java.lang.String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
field public static final int FLAG_EVICT_CE_KEY = 1; // 0x1
field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
@@ -6375,6 +6379,8 @@
field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+ field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+ field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -14947,6 +14953,7 @@
field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -15026,6 +15033,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_STATE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -34396,6 +34404,8 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
+ field public static final java.lang.String ARCHIVED = "archived";
+ field public static final java.lang.String BACKED_UP = "backed_up";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -34403,6 +34413,7 @@
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
+ field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicmail";
field public static final java.lang.String IS_READ = "is_read";
field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -34410,6 +34421,7 @@
field public static final java.lang.String NUMBER = "number";
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String RESTORED = "restored";
field public static final java.lang.String SOURCE_DATA = "source_data";
field public static final java.lang.String SOURCE_PACKAGE = "source_package";
field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -35799,17 +35811,25 @@
ctor public AutoFillService();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
+ method public void onDatasetAuthenticationRequest(android.os.Bundle, int);
method public void onDisconnected();
method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
+ field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
+ field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1
+ field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2
+ field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
public final class FillCallback {
+ method public void onDatasetAuthentication(android.view.autofill.Dataset, int);
method public void onFailure(java.lang.CharSequence);
+ method public void onFillResponseAuthentication(int);
method public void onSuccess(android.view.autofill.FillResponse);
}
@@ -38239,6 +38259,7 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
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_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";
@@ -38854,6 +38875,7 @@
method public int getSimState();
method public int getSimState(int);
method public java.lang.String getSubscriberId();
+ method public java.lang.String getVisualVoicemailPackageName(android.telecom.PhoneAccountHandle);
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
method public int getVoiceNetworkType();
@@ -38906,6 +38928,7 @@
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
+ field public static final java.lang.String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
@@ -38914,6 +38937,7 @@
field public static final java.lang.String EXTRA_STATE_OFFHOOK;
field public static final java.lang.String EXTRA_STATE_RINGING;
field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
+ field public static final java.lang.String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -46603,6 +46627,8 @@
public static final class Dataset.Builder {
ctor public Dataset.Builder(java.lang.CharSequence);
method public android.view.autofill.Dataset build();
+ method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
}
@@ -46618,6 +46644,8 @@
method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
method public android.view.autofill.FillResponse build();
+ method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int);
+ method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3902bd6..823436a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -133,7 +133,7 @@
public final static boolean ENABLE_TASK_SNAPSHOTS;
static {
- ENABLE_TASK_SNAPSHOTS = SystemProperties.getBoolean("persist.enable_task_snapshots", false);
+ ENABLE_TASK_SNAPSHOTS = SystemProperties.getBoolean("persist.enable_task_snapshots", true);
}
static final class UidObserver extends IUidObserver.Stub {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b4fe6b8..34d2039 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -71,7 +71,6 @@
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.NotificationColorUtil;
-import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -982,6 +981,12 @@
public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
/**
+ * {@link #extras} key: whether the notification should be colorized as
+ * supplied to {@link Builder#setColorized(boolean)}}.
+ */
+ public static final String EXTRA_COLORIZED = "android.colorized";
+
+ /**
* {@link #extras} key: the user that built the notification.
*
* @hide
@@ -2438,6 +2443,8 @@
private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
private ArrayList<String> mPersonList = new ArrayList<String>();
private NotificationColorUtil mColorUtil;
+ private boolean mIsLegacy;
+ private boolean mIsLegacyInitialized;
private boolean mColorUtilInited = false;
/**
@@ -2456,6 +2463,10 @@
* so make sure to call {@link StandardTemplateParams#reset()} before using it.
*/
StandardTemplateParams mParams = new StandardTemplateParams();
+ private int mTextColorsAreForBackground = COLOR_INVALID;
+ private int mPrimaryTextColor = COLOR_INVALID;
+ private int mSecondaryTextColor = COLOR_INVALID;
+ private int mActionBarColor = COLOR_INVALID;
/**
* Constructs a new Builder with the defaults:
@@ -2540,7 +2551,7 @@
private NotificationColorUtil getColorUtil() {
if (!mColorUtilInited) {
mColorUtilInited = true;
- if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
+ if (isLegacy() || isColorized()) {
mColorUtil = NotificationColorUtil.getInstance(mContext);
}
}
@@ -3025,6 +3036,22 @@
}
/**
+ * Set whether this notification should be colorized. When set, the color set with
+ * {@link #setColor(int)} will be used as the background color of this notification.
+ * <p>
+ * The coloring will only be applied if the notification is ongoing.
+ * This should only be used for high priority ongoing tasks like navigation, an ongoing
+ * call, or other similarly high-priority events for the user.
+ *
+ * @see Builder#setOngoing(boolean)
+ * @see Builder#setColor(int)
+ */
+ public Builder setColorized(boolean colorize) {
+ mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
+ return this;
+ }
+
+ /**
* Set this flag if you would only like the sound, vibrate
* and ticker to be played if the notification is not already showing.
*
@@ -3389,6 +3416,10 @@
if (profileBadge != null) {
contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
+ if (isColorized()) {
+ contentView.setDrawableParameters(R.id.profile_badge, false, -1,
+ getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP, -1);
+ }
}
}
@@ -3446,13 +3477,14 @@
resetStandardTemplate(contentView);
final Bundle ex = mN.extras;
-
+ updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient);
bindLargeIcon(contentView);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
contentView.setTextViewText(R.id.title, p.title);
+ setTextViewColorPrimary(contentView, R.id.title);
contentView.setViewLayoutWidth(R.id.title, showProgress
? ViewGroup.LayoutParams.WRAP_CONTENT
: ViewGroup.LayoutParams.MATCH_PARENT);
@@ -3461,6 +3493,7 @@
int textId = showProgress ? com.android.internal.R.id.text_line_1
: com.android.internal.R.id.text;
contentView.setTextViewText(textId, p.text);
+ setTextViewColorSecondary(contentView, textId);
contentView.setViewVisibility(textId, View.VISIBLE);
}
@@ -3469,6 +3502,52 @@
return contentView;
}
+ private void setTextViewColorPrimary(RemoteViews contentView, int id) {
+ ensureColors();
+ contentView.setTextColor(id, mPrimaryTextColor);
+ }
+
+ private int getPrimaryTextColor() {
+ ensureColors();
+ return mPrimaryTextColor;
+ }
+
+ private int getActionBarColor() {
+ ensureColors();
+ return mActionBarColor;
+ }
+
+ private void setTextViewColorSecondary(RemoteViews contentView, int id) {
+ ensureColors();
+ contentView.setTextColor(id, mSecondaryTextColor);
+ }
+
+ private void ensureColors() {
+ int backgroundColor = getBackgroundColor();
+ if (mPrimaryTextColor == COLOR_INVALID
+ || mSecondaryTextColor == COLOR_INVALID
+ || mActionBarColor == COLOR_INVALID
+ || mTextColorsAreForBackground != backgroundColor) {
+ mTextColorsAreForBackground = backgroundColor;
+ mPrimaryTextColor = NotificationColorUtil.resolvePrimaryColor(
+ mContext, backgroundColor);
+ mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(
+ mContext, backgroundColor);
+ mActionBarColor = NotificationColorUtil.resolveActionBarColor(backgroundColor);
+ }
+ }
+
+ private void updateBackgroundColor(RemoteViews contentView) {
+ if (isColorized()) {
+ contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
+ getBackgroundColor());
+ } else {
+ // Clear it!
+ contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
+ 0);
+ }
+ }
+
/**
* @param remoteView the remote view to update the minheight in
* @param hasMinHeight does it have a mimHeight
@@ -3535,15 +3614,17 @@
}
private void bindExpandButton(RemoteViews contentView) {
- contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveContrastColor(),
+ int color = isColorized() ? getPrimaryTextColor() : resolveContrastColor();
+ contentView.setDrawableParameters(R.id.expand_button, false, -1, color,
PorterDuff.Mode.SRC_ATOP, -1);
contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
- resolveContrastColor());
+ color);
}
private void bindHeaderChronometerAndTime(RemoteViews contentView) {
if (showsTimeOrChronometer()) {
contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
+ setTextViewColorSecondary(contentView, R.id.time_divider);
if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
contentView.setLong(R.id.chronometer, "setBase",
@@ -3551,9 +3632,11 @@
contentView.setBoolean(R.id.chronometer, "setStarted", true);
boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
contentView.setChronometerCountDown(R.id.chronometer, countsDown);
+ setTextViewColorSecondary(contentView, R.id.chronometer);
} else {
contentView.setViewVisibility(R.id.time, View.VISIBLE);
contentView.setLong(R.id.time, "setTime", mN.when);
+ setTextViewColorSecondary(contentView, R.id.time);
}
} else {
// We still want a time to be set but gone, such that we can show and hide it
@@ -3576,8 +3659,10 @@
if (headerText != null) {
// TODO: Remove the span entirely to only have the string with propper formating.
contentView.setTextViewText(R.id.header_text, processLegacyText(headerText));
+ setTextViewColorSecondary(contentView, R.id.header_text);
contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
+ setTextViewColorSecondary(contentView, R.id.header_text_divider);
}
}
@@ -3616,8 +3701,12 @@
}
private void bindHeaderAppName(RemoteViews contentView, boolean ambient) {
contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
- contentView.setTextColor(R.id.app_name_text,
- ambient ? resolveAmbientColor() : resolveContrastColor());
+ if (isColorized()) {
+ setTextViewColorPrimary(contentView, R.id.app_name_text);
+ } else {
+ contentView.setTextColor(R.id.app_name_text,
+ ambient ? resolveAmbientColor() : resolveContrastColor());
+ }
}
private void bindSmallIcon(RemoteViews contentView, boolean ambient) {
@@ -3675,6 +3764,11 @@
big.setViewVisibility(R.id.actions, View.VISIBLE);
if (p.ambient) {
big.setInt(R.id.actions, "setBackgroundColor", Color.TRANSPARENT);
+ } else if (isColorized()) {
+ big.setInt(R.id.actions, "setBackgroundColor", getActionBarColor());
+ } else {
+ big.setInt(R.id.actions, "setBackgroundColor", mContext.getColor(
+ R.color.notification_action_list));
}
big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
R.dimen.notification_action_list_height);
@@ -3696,15 +3790,18 @@
&& replyText.length > 0 && !TextUtils.isEmpty(replyText[0])) {
big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_1, replyText[0]);
+ setTextViewColorSecondary(big, R.id.notification_material_reply_text_1);
if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])) {
big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_2, replyText[1]);
+ setTextViewColorSecondary(big, R.id.notification_material_reply_text_2);
if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])) {
big.setViewVisibility(
R.id.notification_material_reply_text_3, View.VISIBLE);
big.setTextViewText(R.id.notification_material_reply_text_3, replyText[2]);
+ setTextViewColorSecondary(big, R.id.notification_material_reply_text_3);
}
}
}
@@ -3930,6 +4027,7 @@
if (action.mRemoteInputs != null) {
button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
}
+ // TODO: handle emphasized mode / actions right
if (emphazisedMode) {
// change the background bgColor
int bgColor = mContext.getColor(oddAction ? R.color.notification_action_list
@@ -3945,16 +4043,19 @@
title = ensureColorSpanContrast(title, bgColor, outResultColor);
}
button.setTextViewText(R.id.action0, title);
+ setTextViewColorPrimary(button, R.id.action0);
if (outResultColor != null && outResultColor[0] != null) {
// We need to set the text color as well since changing a text to uppercase
// clears its spans.
button.setTextColor(R.id.action0, outResultColor[0]);
- } else if (mN.color != COLOR_DEFAULT) {
+ } else if (mN.color != COLOR_DEFAULT && !isColorized()) {
button.setTextColor(R.id.action0,resolveContrastColor());
}
} else {
button.setTextViewText(R.id.action0, processLegacyText(action.title));
- if (mN.color != COLOR_DEFAULT) {
+ if (isColorized()) {
+ setTextViewColorPrimary(button, R.id.action0);
+ } else if (mN.color != COLOR_DEFAULT) {
button.setTextColor(R.id.action0,
ambient ? resolveAmbientColor() : resolveContrastColor());
}
@@ -4073,11 +4174,16 @@
* doesn't create material notifications by itself) app.
*/
private boolean isLegacy() {
- return getColorUtil() != null;
+ if (!mIsLegacyInitialized) {
+ mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.LOLLIPOP;
+ mIsLegacyInitialized = true;
+ }
+ return mIsLegacy;
}
private CharSequence processLegacyText(CharSequence charSequence) {
- if (isLegacy()) {
+ if (isLegacy() || isColorized()) {
return getColorUtil().invertCharSequenceColors(charSequence);
} else {
return charSequence;
@@ -4339,6 +4445,37 @@
private int getActionTombstoneLayoutResource() {
return R.layout.notification_material_action_tombstone;
}
+
+ private int getBackgroundColor() {
+ if (isColorized()) {
+ return mN.color;
+ } else {
+ return COLOR_DEFAULT;
+ }
+ }
+
+ private boolean isColorized() {
+ return mN.isColorized();
+ }
+ }
+
+ /**
+ * @return whether this notification is ongoing and can't be dismissed by the user.
+ */
+ private boolean isOngoing() {
+ final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE
+ | Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
+ return (flags & ongoingFlags) != 0;
+ }
+
+ /**
+ * @return true if this notification is colorized. This also factors in wheather the
+ * notification is ongoing.
+ *
+ * @hide
+ */
+ public boolean isColorized() {
+ return extras.getBoolean(EXTRA_COLORIZED) && isOngoing();
}
private boolean hasLargeIcon() {
@@ -4672,6 +4809,7 @@
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
if (mSummaryTextSet) {
contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
+ mBuilder.setTextViewColorSecondary(contentView, R.id.text);
contentView.setViewVisibility(R.id.text, View.VISIBLE);
}
mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon());
@@ -4827,6 +4965,7 @@
static void applyBigTextContentView(Builder builder,
RemoteViews contentView, CharSequence bigTextText) {
contentView.setTextViewText(R.id.big_text, bigTextText);
+ builder.setTextViewColorSecondary(contentView, R.id.big_text);
contentView.setViewVisibility(R.id.big_text,
TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines(builder));
@@ -5070,7 +5209,7 @@
: (m == null) ? null : m.mSender;
CharSequence text = (m == null)
? null
- : mConversationTitle != null ? makeMessageLine(m) : m.mText;
+ : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
return mBuilder.applyStandardTemplateWithActions(mBuilder.getBaseLayoutResource(),
mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
@@ -5108,7 +5247,7 @@
CharSequence text;
if (hasTitle) {
bigTitle = title;
- text = makeMessageLine(mMessages.get(0));
+ text = makeMessageLine(mMessages.get(0), mBuilder);
} else {
bigTitle = mMessages.get(0).mSender;
text = mMessages.get(0).mText;
@@ -5146,7 +5285,7 @@
Message m = mHistoricMessages.get(firstHistoricMessage + i);
int rowId = rowIds[i];
- contentView.setTextViewText(rowId, makeMessageLine(m));
+ contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
if (contractedMessage == m) {
contractedChildId = rowId;
@@ -5161,7 +5300,8 @@
int rowId = rowIds[i];
contentView.setViewVisibility(rowId, View.VISIBLE);
- contentView.setTextViewText(rowId, makeMessageLine(m));
+ contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
+ mBuilder.setTextViewColorSecondary(contentView, rowId);
if (contractedMessage == m) {
contractedChildId = rowId;
@@ -5183,17 +5323,22 @@
return contentView;
}
- private CharSequence makeMessageLine(Message m) {
+ private CharSequence makeMessageLine(Message m, Builder builder) {
BidiFormatter bidi = BidiFormatter.getInstance();
SpannableStringBuilder sb = new SpannableStringBuilder();
+ boolean colorize = builder.isColorized();
if (TextUtils.isEmpty(m.mSender)) {
CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
sb.append(bidi.unicodeWrap(replyName),
- makeFontColorSpan(mBuilder.resolveContrastColor()),
+ makeFontColorSpan(colorize
+ ? builder.getPrimaryTextColor()
+ : mBuilder.resolveContrastColor()),
0 /* flags */);
} else {
sb.append(bidi.unicodeWrap(m.mSender),
- makeFontColorSpan(Color.BLACK),
+ makeFontColorSpan(colorize
+ ? builder.getPrimaryTextColor()
+ : Color.BLACK),
0 /* flags */);
}
CharSequence text = m.mText == null ? "" : m.mText;
@@ -5212,7 +5357,7 @@
: (m == null) ? null : m.mSender;
CharSequence text = (m == null)
? null
- : mConversationTitle != null ? makeMessageLine(m) : m.mText;
+ : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
@@ -5503,6 +5648,7 @@
if (!TextUtils.isEmpty(str)) {
contentView.setViewVisibility(rowIds[i], View.VISIBLE);
contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str));
+ mBuilder.setTextViewColorSecondary(contentView, rowIds[i]);
contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
handleInboxImageMargin(contentView, rowIds[i], first);
if (first) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 72daade..0ff8550 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1091,6 +1091,30 @@
public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
/**
+ * Constant to indicate the feature of disabling the camera. Used as argument to
+ * {@link #createAdminSupportIntent(String)}.
+ * @see #setCameraDisabled(ComponentName, boolean)
+ */
+ public static final String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+
+ /**
+ * Constant to indicate the feature of disabling screen captures. Used as argument to
+ * {@link #createAdminSupportIntent(String)}.
+ * @see #setScreenCaptureDisabled(ComponentName, boolean)
+ */
+ public static final String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
+
+ /**
+ * A String indicating a specific restricted feature. Can be a user restriction from the
+ * {@link UserManager}, e.g. {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the values
+ * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @see #createAdminSupportIntent(String)
+ * @hide
+ */
+ @TestApi
+ public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
+
+ /**
* Activity action: have the user enter a new password. This activity should
* be launched after using {@link #setPasswordQuality(ComponentName, int)},
* or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user
@@ -5827,6 +5851,33 @@
}
/**
+ * Called by any app to display a support dialog when a feature was disabled by an admin.
+ * This returns an intent that can be used with {@link Context#startActivity(Intent)} to
+ * display the dialog. It will tell the user that the feature indicated by {@code restriction}
+ * was disabled by an admin, and include a link for more information. The default content of
+ * the dialog can be changed by the restricting admin via
+ * {@link #setShortSupportMessage(ComponentName, CharSequence)}. If the restriction is not
+ * set (i.e. the feature is available), then the return value will be {@code null}.
+ * @param restriction Indicates for which feature the dialog should be displayed. Can be a
+ * user restriction from {@link UserManager}, e.g.
+ * {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the constants
+ * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @return Intent An intent to be used to start the dialog-activity if the restriction is
+ * set by an admin, or null if the restriction does not exist or no admin set it.
+ */
+ public Intent createAdminSupportIntent(@NonNull String restriction) {
+ throwIfParentInstance("createAdminSupportIntent");
+ if (mService != null) {
+ try {
+ return mService.createAdminSupportIntent(restriction);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and
* actual package file remain. This function can be called by a device owner, profile owner, or
* by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7604af8..79fe10e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -201,6 +201,7 @@
List getPermittedInputMethodsForCurrentUser();
boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId);
+ Intent createAdminSupportIntent(in String restriction);
boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden);
boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName);
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 98a8904..12b46c1 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1625,6 +1625,34 @@
new Key<Integer>("android.control.postRawSensitivityBoost", int.class);
/**
+ * <p>Allow camera device to enable zero-shutter-lag mode for requests with
+ * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE.</p>
+ * <p>If enableZsl is <code>true</code>, the camera device may enable zero-shutter-lag mode for requests with
+ * STILL_CAPTURE capture intent. The camera device may use images captured in the past to
+ * produce output images for a zero-shutter-lag request. The result metadata including the
+ * {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} reflects the source frames used to produce output images.
+ * Therefore, the contents of the output images and the result metadata may be out of order
+ * compared to previous regular requests. enableZsl does not affect requests with other
+ * capture intents.</p>
+ * <p>For example, when requests are submitted in the following order:
+ * Request A: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is PREVIEW
+ * Request B: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is STILL_CAPTURE</p>
+ * <p>The output images for request B may have contents captured before the output images for
+ * request A, and the result metadata for request B may be older than the result metadata for
+ * request A.</p>
+ * <p>Note that when enableZsl is <code>true</code>, it is not guaranteed to get output images captured in the
+ * past for requests with STILL_CAPTURE capture intent.</p>
+ * <p>The value of enableZsl in capture templates is always <code>false</code> if present.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
+ * @see CaptureResult#SENSOR_TIMESTAMP
+ */
+ @PublicKey
+ public static final Key<Boolean> CONTROL_ENABLE_ZSL =
+ new Key<Boolean>("android.control.enableZsl", boolean.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 23f4b9e..3f8b57a 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2132,6 +2132,34 @@
new Key<Integer>("android.control.postRawSensitivityBoost", int.class);
/**
+ * <p>Allow camera device to enable zero-shutter-lag mode for requests with
+ * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE.</p>
+ * <p>If enableZsl is <code>true</code>, the camera device may enable zero-shutter-lag mode for requests with
+ * STILL_CAPTURE capture intent. The camera device may use images captured in the past to
+ * produce output images for a zero-shutter-lag request. The result metadata including the
+ * {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} reflects the source frames used to produce output images.
+ * Therefore, the contents of the output images and the result metadata may be out of order
+ * compared to previous regular requests. enableZsl does not affect requests with other
+ * capture intents.</p>
+ * <p>For example, when requests are submitted in the following order:
+ * Request A: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is PREVIEW
+ * Request B: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is STILL_CAPTURE</p>
+ * <p>The output images for request B may have contents captured before the output images for
+ * request A, and the result metadata for request B may be older than the result metadata for
+ * request A.</p>
+ * <p>Note that when enableZsl is <code>true</code>, it is not guaranteed to get output images captured in the
+ * past for requests with STILL_CAPTURE capture intent.</p>
+ * <p>The value of enableZsl in capture templates is always <code>false</code> if present.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
+ * @see CaptureResult#SENSOR_TIMESTAMP
+ */
+ @PublicKey
+ public static final Key<Boolean> CONTROL_ENABLE_ZSL =
+ new Key<Boolean>("android.control.enableZsl", boolean.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 4b57078..2c9e6c7 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1074,7 +1074,7 @@
@Override // binder call
public void onAuthenticationFailed(long deviceId) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();;
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
}
@Override // binder call
diff --git a/core/java/android/net/NetworkBadging.java b/core/java/android/net/NetworkBadging.java
new file mode 100644
index 0000000..5cf2f96
--- /dev/null
+++ b/core/java/android/net/NetworkBadging.java
@@ -0,0 +1,152 @@
+/*
+ * 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.net;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.net.ScoredNetwork.Badging;
+import android.net.wifi.WifiManager;
+import android.view.View;
+
+/**
+ * Utility methods for working with network badging.
+ *
+ * TODO: move ScoredNetwork.Badging and related constants to this class.
+ *
+ * @hide
+ */
+@SystemApi
+public class NetworkBadging {
+ private NetworkBadging() {}
+
+ /**
+ * Returns a Wi-Fi icon for a network with a given signal level and badging value.
+ *
+ * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+ * for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+ * @param badging {@see ScoredNetwork#Badging}, retrieved from
+ * {@link ScoredNetwork#calculateBadge(int)}.
+ * @param theme The theme for the current application, may be null.
+ * @return Drawable for the given icon
+ * @throws IllegalArgumentException if {@code signalLevel} is out of range or {@code badging}
+ * is an invalid value
+ */
+ @NonNull public static Drawable getWifiIcon(
+ @IntRange(from=0, to=4) int signalLevel, @Badging int badging, @Nullable Theme theme) {
+ Resources resources = Resources.getSystem();
+ if (badging == ScoredNetwork.BADGING_NONE) {
+ return resources.getDrawable(getWifiSignalResource(signalLevel), theme);
+ }
+ Drawable[] layers = new Drawable[] {
+ resources.getDrawable(getBadgedWifiSignalResource(signalLevel), theme),
+ resources.getDrawable(getWifiBadgeResource(badging), theme)
+ };
+ return new LayerDrawable(layers);
+ }
+
+ /**
+ * Returns the wifi signal resource id for the given signal level.
+ *
+ * <p>This wifi signal resource is a wifi icon to be displayed by itself when there is no badge.
+ *
+ * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+ * for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+ * @return the @DrawableRes for the icon
+ * @throws IllegalArgumentException for an invalid signal level
+ * @hide
+ */
+ @DrawableRes private static int getWifiSignalResource(int signalLevel) {
+ switch (signalLevel) {
+ case 0:
+ return com.android.internal.R.drawable.ic_wifi_signal_0;
+ case 1:
+ return com.android.internal.R.drawable.ic_wifi_signal_1;
+ case 2:
+ return com.android.internal.R.drawable.ic_wifi_signal_2;
+ case 3:
+ return com.android.internal.R.drawable.ic_wifi_signal_3;
+ case 4:
+ return com.android.internal.R.drawable.ic_wifi_signal_4;
+ default:
+ throw new IllegalArgumentException("Invalid signal level: " + signalLevel);
+ }
+ }
+
+ /**
+ * Returns the badged wifi signal resource id for the given signal level.
+ *
+ * <p>This badged wifi signal resource should be displayed with the quality badge retrieved
+ * from {@link #getWifiBadgeResource(int)}. If there is no badge,
+ * {@link #getWifiBadgeResource(int)} should be used instead of this method.
+ *
+ * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+ * for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+ * @return the @DrawableRes for the icon
+ * @throws IllegalArgumentException for an invalid signal level
+ * @hide
+ */
+ @DrawableRes private static int getBadgedWifiSignalResource(int signalLevel) {
+ switch (signalLevel) {
+ case 0:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_0_bars;
+ case 1:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_1_bar;
+ case 2:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_2_bars;
+ case 3:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_3_bars;
+ case 4:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_4_bars;
+ default:
+ throw new IllegalArgumentException("Invalid signal level: " + signalLevel);
+ }
+ }
+
+ /**
+ * Returns the wifi quality badge resource id for the the given badging balue.
+ *
+ * <p>This badge should be displayed with the badge signal resource retrieved from
+ * {@link #getBadgedWifiSignalResource(int)}.
+ *
+ * @param badging {@see ScoredNetwork#Badging} from {@link ScoredNetwork#calculateBadge(int)}.
+ * @return the @DrawableRes for the icon or {@link View#NO_ID} for
+ * {@link ScoredNetwork#BADGING_NONE}
+ * @throws IllegalArgumentException for an invalid badging value.
+ * @hide
+ */
+ @DrawableRes private static int getWifiBadgeResource(@Badging int badging) {
+ switch (badging) {
+ case ScoredNetwork.BADGING_NONE:
+ return View.NO_ID;
+ case ScoredNetwork.BADGING_SD:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_sd;
+ case ScoredNetwork.BADGING_HD:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_hd;
+ case ScoredNetwork.BADGING_4K:
+ return com.android.internal.R.drawable.ic_signal_wifi_badged_4k;
+ default:
+ throw new IllegalArgumentException("No resource found for badge: " + badging);
+ }
+ }
+}
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index e48a0d0..a8b094e 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -284,8 +284,6 @@
* not.
*
* <P>Type: INTEGER (boolean)</P>
- *
- * @hide
*/
public static final String BACKED_UP = "backed_up";
@@ -294,8 +292,6 @@
* restored, 0 if not.
*
* <P>Type: INTEGER (boolean)</P>
- *
- * @hide
*/
public static final String RESTORED = "restored";
@@ -305,19 +301,19 @@
* if not.
*
* <P>Type: INTEGER (boolean)</P>
- *
- * @hide
*/
public static final String ARCHIVED = "archived";
/**
* Flag to indicate the voicemail is a OMTP voicemail handled by the {@link
* android.telephony.VisualVoicemailService}. The UI should only show OMTP voicemails from
- * the current visual voicemail package.
+ * the current visual voicemail package. For example, the selection could be
+ * {@code WHERE (IS_OMTP_VOICEMAIL == 0) OR ( IS_OMTP_VOICEMAIL == 1 AND SOURCE_PACKAGE ==
+ * "current.vvm.package")}
*
* <P>Type: INTEGER (boolean)</P>
*
- * @hide
+ * @see android.telephony.TelephonyManager#getVisualVoicemailPackageName
*/
public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index c7c8321..b5cb8f8 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -30,13 +30,19 @@
import android.os.Message;
import android.util.Log;
import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
-// TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the
-// life-cycle (and how state could be maintained on server-side) is well documented.
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+//TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the
+//life-cycle (and how state could be maintained on server-side) is well documented.
/**
* Top-level service of the current auto-fill service for a given user.
@@ -48,6 +54,9 @@
private static final String TAG = "AutoFillService";
static final boolean DEBUG = true; // TODO(b/33197203): set to false once stable
+ // TODO(b/33197203): check for device's memory size instead of DEBUG?
+ static final boolean DEBUG_PENDING_CALLBACKS = DEBUG;
+
/**
* The {@link Intent} that must be declared as handled by the service.
* To be supported, the service must also require the
@@ -73,6 +82,7 @@
// Internal bundle keys.
/** @hide */ public static final String KEY_CALLBACK = "callback";
/** @hide */ public static final String KEY_SAVABLE_IDS = "savable_ids";
+ /** @hide */ public static final String EXTRA_CRYPTO_OBJECT_ID = "crypto_object_id";
// Prefix for public bundle keys.
private static final String KEY_PREFIX = "android.service.autofill.extra.";
@@ -93,10 +103,32 @@
*/
public static final String EXTRA_DATASET_EXTRAS = KEY_PREFIX + "DATASET_EXTRAS";
+ /**
+ * Used to indicate the user selected an action that requires authentication.
+ */
+ public static final int FLAG_AUTHENTICATION_REQUESTED = 1 << 0;
+
+ /**
+ * Used to indicate the user authentication succeeded.
+ */
+ public static final int FLAG_AUTHENTICATION_SUCCESS = 1 << 1;
+
+ /**
+ * Used to indicate the user authentication failed.
+ */
+ public static final int FLAG_AUTHENTICATION_ERROR = 1 << 2;
+
+ /**
+ * Used when the service requested Fingerprint authentication but such option is not available.
+ */
+ public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 1 << 3;
+
// Handler messages.
private static final int MSG_CONNECT = 1;
- private static final int MSG_AUTO_FILL_ACTIVITY = 2;
- private static final int MSG_DISCONNECT = 3;
+ private static final int MSG_DISCONNECT = 2;
+ private static final int MSG_AUTO_FILL_ACTIVITY = 3;
+ private static final int MSG_AUTHENTICATE_FILL_RESPONSE = 4;
+ private static final int MSG_AUTHENTICATE_DATASET = 5;
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@@ -109,6 +141,22 @@
}
@Override
+ public void authenticateFillResponse(Bundle extras, int flags) {
+ final Message msg = mHandlerCaller.obtainMessage(MSG_AUTHENTICATE_FILL_RESPONSE);
+ msg.arg1 = flags;
+ msg.obj = extras;
+ mHandlerCaller.sendMessage(msg);
+ }
+
+ @Override
+ public void authenticateDataset(Bundle extras, int flags) {
+ final Message msg = mHandlerCaller.obtainMessage(MSG_AUTHENTICATE_DATASET);
+ msg.arg1 = flags;
+ msg.obj = extras;
+ mHandlerCaller.sendMessage(msg);
+ }
+
+ @Override
public void onConnected() {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CONNECT));
}
@@ -135,6 +183,16 @@
final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg3;
requestAutoFill(callback, structure, extras, flags);
break;
+ } case MSG_AUTHENTICATE_FILL_RESPONSE: {
+ final int flags = msg.arg1;
+ final Bundle extras = (Bundle) msg.obj;
+ onFillResponseAuthenticationRequest(extras, flags);
+ break;
+ } case MSG_AUTHENTICATE_DATASET: {
+ final int flags = msg.arg1;
+ final Bundle extras = (Bundle) msg.obj;
+ onDatasetAuthenticationRequest(extras, flags);
+ break;
} case MSG_DISCONNECT: {
onDisconnected();
break;
@@ -147,6 +205,10 @@
private HandlerCaller mHandlerCaller;
+ // User for debugging purposes
+ private final List<CallbackHelper.Dumpable> mPendingCallbacks =
+ DEBUG_PENDING_CALLBACKS ? new ArrayList<>() : null;
+
/**
* {@inheritDoc}
*
@@ -211,16 +273,84 @@
public abstract void onSaveRequest(AssistStructure structure,
Bundle data, CancellationSignal cancellationSignal, SaveCallback callback);
+ /**
+ * Called as result of the user action for a {@link FillResponse} that required authentication.
+ *
+ * <p>When the {@link FillResponse} required authentication through
+ * {@link android.view.autofill.FillResponse.Builder#requiresCustomAuthentication(Bundle, int)}, this
+ * call indicates the user is requesting the service to authenticate him/her (and {@code flags}
+ * contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains the
+ * {@link Bundle} passed to that method.
+ *
+ * <p>When the {@link FillResponse} required authentication through
+ * {@link android.view.autofill.FillResponse.Builder#requiresFingerprintAuthentication(
+ * android.hardware.fingerprint.FingerprintManager.CryptoObject, Bundle, int)},
+ * {@code flags} this call contains the result of the fingerprint authentication (such as
+ * {@link #FLAG_AUTHENTICATION_SUCCESS}, {@link #FLAG_AUTHENTICATION_ERROR}, and
+ * {@link #FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}) and {@code extras} contains the
+ * {@link Bundle} passed to that method.
+ */
+ public void onFillResponseAuthenticationRequest(@SuppressWarnings("unused") Bundle extras,
+ int flags) {
+ if (DEBUG) Log.d(TAG, "onFillResponseAuthenticationRequest(): flags=" + flags);
+ }
+
+ /**
+ * Called as result of the user action for a {@link Dataset} that required authentication.
+ *
+ * <p>When the {@link Dataset} required authentication through
+ * {@link android.view.autofill.Dataset.Builder#requiresCustomAuthentication(Bundle, int)}, this
+ * call indicates the user is requesting the service to authenticate him/her (and {@code flags}
+ * contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains the
+ * {@link Bundle} passed to that method.
+ *
+ * <p>When the {@link Dataset} required authentication through
+ * {@link android.view.autofill.Dataset.Builder#requiresFingerprintAuthentication(
+ * android.hardware.fingerprint.FingerprintManager.CryptoObject, Bundle, int)},
+ * {@code flags} this call contains the result of the fingerprint authentication (such as
+ * {@link #FLAG_AUTHENTICATION_SUCCESS}, {@link #FLAG_AUTHENTICATION_ERROR}, and
+ * {@link #FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}) and {@code extras} contains the
+ * {@link Bundle} passed to that method.
+ */
+ public void onDatasetAuthenticationRequest(@SuppressWarnings("unused") Bundle extras,
+ int flags) {
+ if (DEBUG) Log.d(TAG, "onDatasetAuthenticationRequest(): flags=" + flags);
+ }
+
+ // TODO(b/33197203): make it final and create another method classes could extend so it's
+ // guaranteed to dump the pending callbacks?
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mPendingCallbacks != null) {
+ pw.print("Number of pending callbacks: "); pw.println(mPendingCallbacks.size());
+ final String prefix = " ";
+ for (int i = 0; i < mPendingCallbacks.size(); i++) {
+ final CallbackHelper.Dumpable cb = mPendingCallbacks.get(i);
+ pw.print('#'); pw.print(i + 1); pw.println(':');
+ cb.dump(prefix, pw);
+ }
+ pw.println();
+ } else {
+ pw.println("Dumping disabled");
+ }
+ }
+
private void requestAutoFill(IAutoFillServerCallback callback, AssistStructure structure,
Bundle data, int flags) {
switch (flags) {
case AUTO_FILL_FLAG_TYPE_FILL:
final FillCallback fillCallback = new FillCallback(callback);
+ if (DEBUG_PENDING_CALLBACKS) {
+ addPendingCallback(fillCallback);
+ }
// TODO(b/33197203): hook up the cancelationSignal
onFillRequest(structure, data, new CancellationSignal(), fillCallback);
break;
case AUTO_FILL_FLAG_TYPE_SAVE:
final SaveCallback saveCallback = new SaveCallback(callback);
+ if (DEBUG_PENDING_CALLBACKS) {
+ addPendingCallback(saveCallback);
+ }
// TODO(b/33197203): hook up the cancelationSignal
onSaveRequest(structure, data, new CancellationSignal(), saveCallback);
break;
@@ -229,6 +359,22 @@
}
}
+ private void addPendingCallback(CallbackHelper.Dumpable callback) {
+ if (mPendingCallbacks == null) {
+ // Shouldn't happend since call is controlled by DEBUG_PENDING_CALLBACKS guard.
+ Log.wtf(TAG, "addPendingCallback(): mPendingCallbacks not set");
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Adding pending callback: " + callback);
+
+ callback.setFinalizer(() -> {
+ if (DEBUG) Log.d(TAG, "Removing pending callback: " + callback);
+ mPendingCallbacks.remove(callback);
+ });
+ mPendingCallbacks.add(callback);
+ }
+
/**
* Called when the Android system disconnects from the service.
*
diff --git a/core/java/android/service/autofill/CallbackHelper.java b/core/java/android/service/autofill/CallbackHelper.java
new file mode 100644
index 0000000..ded8f97
--- /dev/null
+++ b/core/java/android/service/autofill/CallbackHelper.java
@@ -0,0 +1,30 @@
+/*
+ * 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.service.autofill;
+
+import java.io.PrintWriter;
+
+final class CallbackHelper {
+
+ static interface Dumpable {
+ void dump(String prefix, PrintWriter pw);
+ void setFinalizer(Finalizer f);
+ }
+
+ static interface Finalizer {
+ void gone();
+ }
+}
diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java
index 925da8b..7cab7ae 100644
--- a/core/java/android/service/autofill/FillCallback.java
+++ b/core/java/android/service/autofill/FillCallback.java
@@ -17,27 +17,49 @@
package android.service.autofill;
import static android.service.autofill.AutoFillService.DEBUG;
+import static android.util.DebugUtils.flagsToString;
import android.annotation.Nullable;
import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
+import android.service.autofill.CallbackHelper.Dumpable;
+import android.service.autofill.CallbackHelper.Finalizer;
import android.util.Log;
+import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.io.PrintWriter;
+
/**
* Handles auto-fill requests from the {@link AutoFillService} into the {@link Activity} being
* auto-filled.
+ *
+ * <p>This class is thread safe.
*/
-public final class FillCallback {
+public final class FillCallback implements Dumpable {
private static final String TAG = "FillCallback";
+ // NOTE: constants below are public so they can be used by flagsToString()
+ /** @hide */ public static final int STATE_INITIAL = 1 << 0;
+ /** @hide */ public static final int STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE = 1 << 1;
+ /** @hide */ public static final int STATE_WAITING_DATASET_AUTH_RESPONSE = 1 << 2;
+ /** @hide */ public static final int STATE_FINISHED_OK = 1 << 3;
+ /** @hide */ public static final int STATE_FINISHED_FAILURE = 1 << 4;
+ /** @hide */ public static final int STATE_FINISHED_ERROR = 1 << 5;
+ /** @hide */ public static final int STATE_FINISHED_AUTHENTICATED = 1 << 6;
+
private final IAutoFillServerCallback mCallback;
- private boolean mReplied = false;
+ @GuardedBy("mCallback")
+ private int mState = STATE_INITIAL;
+
+ @GuardedBy("mCallback")
+ private Finalizer mFinalizer;
/** @hide */
FillCallback(IAutoFillServerCallback callback) {
@@ -46,51 +68,174 @@
/**
* Notifies the Android System that an
- * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, FillCallback)}
- * was successfully fulfilled by the service.
+ * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle,
+ * android.os.CancellationSignal, FillCallback)} was successfully fulfilled by the service.
*
* @param response auto-fill information for that activity, or {@code null} when the activity
- * cannot be auto-filled (for example, if it only contains read-only fields).
- *
- * @throws RuntimeException if an error occurred while calling the Android System.
+ * cannot be auto-filled (for example, if it only contains read-only fields). See
+ * {@link FillResponse} for examples.
*/
public void onSuccess(@Nullable FillResponse response) {
- if (DEBUG) Log.d(TAG, "onSuccess(): respose=" + response);
+ final boolean authRequired = response != null && response.isAuthRequired();
- checkNotRepliedYet();
+ if (DEBUG) Log.d(TAG, "onSuccess(): authReq= " + authRequired + ", resp=" + response);
- try {
- mCallback.showResponse(response);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ synchronized (mCallback) {
+ if (authRequired) {
+ assertOnStateLocked(STATE_INITIAL);
+ } else {
+ assertOnStateLocked(STATE_INITIAL | STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE
+ | STATE_WAITING_DATASET_AUTH_RESPONSE);
+ }
+
+ try {
+ mCallback.showResponse(response);
+ if (authRequired) {
+ mState = STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE;
+ } else {
+ // Check if at least one dataset requires authentication.
+ boolean waitingAuth = false;
+ if (response != null) {
+ for (Dataset dataset : response.getDatasets()) {
+ if (dataset.isAuthRequired()) {
+ waitingAuth = true;
+ break;
+ }
+ }
+ }
+ if (waitingAuth) {
+ mState = STATE_WAITING_DATASET_AUTH_RESPONSE;
+ } else {
+ setFinalStateLocked(STATE_FINISHED_OK);
+ }
+ }
+ } catch (RemoteException e) {
+ setFinalStateLocked(STATE_FINISHED_ERROR);
+ e.rethrowAsRuntimeException();
+ }
}
}
/**
* Notifies the Android System that an
- * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, FillCallback)}
+ * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, FillCallback)}
* could not be fulfilled by the service.
*
* @param message error message to be displayed to the user.
- *
- * @throws RuntimeException if an error occurred while calling the Android System.
*/
public void onFailure(CharSequence message) {
if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
- checkNotRepliedYet();
Preconditions.checkArgument(message != null, "message cannot be null");
- try {
- mCallback.showError(message.toString());
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ synchronized (mCallback) {
+ assertOnStateLocked(STATE_INITIAL | STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE
+ | STATE_WAITING_DATASET_AUTH_RESPONSE);
+
+ try {
+ mCallback.showError(message);
+ setFinalStateLocked(STATE_FINISHED_FAILURE);
+ } catch (RemoteException e) {
+ setFinalStateLocked(STATE_FINISHED_ERROR);
+ e.rethrowAsRuntimeException();
+ }
}
}
- // There can be only one!!
- private void checkNotRepliedYet() {
- Preconditions.checkState(!mReplied, "already replied");
- mReplied = true;
+ /**
+ * Notifies the Android System when the user authenticated a {@link FillResponse} previously
+ * passed to {@link #onSuccess(FillResponse)}.
+ *
+ * @param flags must contain either
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} or
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}.
+ */
+ public void onFillResponseAuthentication(int flags) {
+ if (DEBUG) Log.d(TAG, "onFillResponseAuthentication(): flags=" + flags);
+
+ synchronized (mCallback) {
+ assertOnStateLocked(STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE);
+
+ try {
+ mCallback.unlockFillResponse(flags);
+ setFinalStateLocked(STATE_FINISHED_AUTHENTICATED);
+ } catch (RemoteException e) {
+ setFinalStateLocked(STATE_FINISHED_ERROR);
+ e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ /**
+ * Notifies the Android System when the user authenticated a {@link Dataset} previously passed
+ * to {@link #onSuccess(FillResponse)}.
+ *
+ * @param dataset values to fill the activity with in case of successful authentication of a
+ * previously locked (and empty) dataset).
+ * @param flags must contain either
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} or
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}.
+ */
+ public void onDatasetAuthentication(@Nullable Dataset dataset, int flags) {
+ if (DEBUG) Log.d(TAG, "onDatasetAuthentication(): dataset=" + dataset + ", flags=" + flags);
+
+ synchronized (mCallback) {
+ assertOnStateLocked(STATE_WAITING_DATASET_AUTH_RESPONSE);
+
+ try {
+ mCallback.unlockDataset(dataset, flags);
+ setFinalStateLocked(STATE_FINISHED_AUTHENTICATED);
+ } catch (RemoteException e) {
+ setFinalStateLocked(STATE_FINISHED_ERROR);
+ e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ return "FillCallback: [mState = " + mState + "]";
+ }
+
+ /** @hide */
+ @Override
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("FillCallback: mState="); pw.println(mState);
+ }
+
+ /** @hide */
+ @Override
+ public void setFinalizer(Finalizer f) {
+ synchronized (mCallback) {
+ mFinalizer = f;
+ }
+ }
+
+ /**
+ * Sets a final state (where the callback cannot be used anymore) and notifies the
+ * {@link Finalizer} (if any).
+ */
+ private void setFinalStateLocked(int state) {
+ if (DEBUG) Log.d(TAG, "setFinalState(): " + state);
+ mState = state;
+
+ if (mFinalizer != null) {
+ mFinalizer.gone();
+ }
+ }
+
+ // TODO(b/33197203): move and/or re-add state check logic on server side to avoid malicious app
+ // calling the callback on wrong state.
+
+ // Make sure callback method is called during the proper lifecycle state.
+ private void assertOnStateLocked(int flags) {
+ if (DEBUG) Log.d(TAG, "assertOnState(): current=" + mState + ", required=" + flags);
+
+ Preconditions.checkState((flags & mState) != 0,
+ "invalid state: required " + flagsToString(FillCallback.class, "STATE_", flags)
+ + ", current is " + flagsToString(FillCallback.class, "STATE_", mState));
}
}
diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
index 629b1f0..cc83776 100644
--- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
@@ -21,8 +21,15 @@
import android.view.autofill.Dataset;
/**
+ * Object running in the application process and responsible for auto-filling it.
+ *
* @hide
*/
+// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
+// itself
oneway interface IAutoFillAppCallback {
+ /**
+ * Auto-fills the activity with the contents of a dataset.
+ */
void autoFill(in Dataset dataset);
}
diff --git a/core/java/android/service/autofill/IAutoFillServerCallback.aidl b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
index 9d58c99..185c8f3 100644
--- a/core/java/android/service/autofill/IAutoFillServerCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
@@ -18,14 +18,23 @@
import java.util.List;
+import android.os.Bundle;
import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
/**
+ * Object running in the AutoFillService process and used to communicate back with system_server.
+ *
* @hide
*/
+// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
+// itself
oneway interface IAutoFillServerCallback {
+ // TODO(b/33197203): document methods
void showResponse(in FillResponse response);
- void showError(String message);
+ void showError(CharSequence message);
void highlightSavedFields(in AutoFillId[] ids);
+ void unlockFillResponse(int flags);
+ void unlockDataset(in Dataset dataset, int flags);
}
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index a1f22bf..fa9786a 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -24,9 +24,13 @@
/**
* @hide
*/
+// TODO(b/33197203): document class and methods
oneway interface IAutoFillService {
+ // TODO(b/33197203): rename method to make them more consistent
void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback,
in Bundle extras, int flags);
+ void authenticateFillResponse(in Bundle extras, int flags);
+ void authenticateDataset(in Bundle extras, int flags);
void onConnected();
void onDisconnected();
}
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 04f8c9f..d5022d8 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -22,23 +22,34 @@
import android.app.assist.AssistStructure.ViewNode;
import android.os.Bundle;
import android.os.RemoteException;
+import android.service.autofill.CallbackHelper.Dumpable;
+import android.service.autofill.CallbackHelper.Finalizer;
import android.util.Log;
import android.view.autofill.AutoFillId;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.io.PrintWriter;
+
/**
* Handles save requests from the {@link AutoFillService} into the {@link Activity} being
* auto-filled.
+ *
+ * <p>This class is thread safe.
*/
-public final class SaveCallback {
+public final class SaveCallback implements Dumpable {
private static final String TAG = "SaveCallback";
private final IAutoFillServerCallback mCallback;
+ @GuardedBy("mCallback")
private boolean mReplied = false;
+ @GuardedBy("mCallback")
+ private Finalizer mFinalizer;
+
/** @hide */
SaveCallback(IAutoFillServerCallback callback) {
mCallback = callback;
@@ -46,8 +57,8 @@
/**
* Notifies the Android System that an
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, SaveCallback)}
- * was successfully fulfilled by the service.
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+ * android.os.CancellationSignal, SaveCallback)} was successfully fulfilled by the service.
*
* @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved.
*
@@ -57,21 +68,24 @@
if (DEBUG) Log.d(TAG, "onSuccess(): ids=" + ((ids == null) ? "null" : ids.length));
Preconditions.checkArgument(ids != null, "ids cannot be null");
- checkNotRepliedYet();
-
Preconditions.checkArgument(ids.length > 0, "ids cannot be empty");
- try {
- mCallback.highlightSavedFields(ids);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ synchronized (mCallback) {
+ checkNotRepliedYetLocked();
+ try {
+ mCallback.highlightSavedFields(ids);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ } finally {
+ setRepliedLocked();
+ }
}
}
/**
* Notifies the Android System that an
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, SaveCallback)}
- * could not be fulfilled by the service.
+ * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+ * android.os.CancellationSignal, SaveCallback)} could not be fulfilled by the service.
*
* @param message error message to be displayed to the user.
*
@@ -81,18 +95,53 @@
if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
Preconditions.checkArgument(message != null, "message cannot be null");
- checkNotRepliedYet();
- try {
- mCallback.showError(message.toString());
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ synchronized (mCallback) {
+ checkNotRepliedYetLocked();
+
+ try {
+ mCallback.showError(message);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ } finally {
+ setRepliedLocked();
+ }
}
}
+ /** @hide */
+ @Override
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("SaveCallback: mReplied="); pw.println(mReplied);
+ }
+
+ /** @hide */
+ @Override
+ public void setFinalizer(Finalizer f) {
+ synchronized (mCallback) {
+ mFinalizer = f;
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (!DEBUG) return super.toString();
+
+ return "SaveCallback: [mReplied= " + mReplied + "]";
+ }
+
// There can be only one!!
- private void checkNotRepliedYet() {
+ private void checkNotRepliedYetLocked() {
Preconditions.checkState(!mReplied, "already replied");
+ }
+
+ private void setRepliedLocked() {
+ if (DEBUG) Log.d(TAG, "setReplied()");
+
mReplied = true;
+
+ if (mFinalizer != null) {
+ mFinalizer.gone();
+ }
}
}
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 37d6757..0a294ab 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -19,7 +19,6 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.SystemClock;
-import android.text.format.DateUtils;
import com.android.internal.util.XmlUtils;
diff --git a/core/java/android/view/autofill/Dataset.java b/core/java/android/view/autofill/Dataset.java
index a73eb774..b11eecc 100644
--- a/core/java/android/view/autofill/Dataset.java
+++ b/core/java/android/view/autofill/Dataset.java
@@ -19,12 +19,13 @@
import static android.view.autofill.Helper.DEBUG;
import static android.view.autofill.Helper.append;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.assist.AssistStructure.ViewNode;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.service.autofill.AutoFillService;
import com.android.internal.util.Preconditions;
@@ -50,12 +51,20 @@
private final CharSequence mName;
private final ArrayList<DatasetField> mFields;
private final Bundle mExtras;
+ private final int mFlags;
+ private final boolean mRequiresAuth;
+ private final boolean mHasCryptoObject;
+ private final long mCryptoOpId;
private Dataset(Dataset.Builder builder) {
mName = builder.mName;
// TODO(b/33197203): make an immutable copy of mFields?
mFields = builder.mFields;
mExtras = builder.mExtras;
+ mFlags = builder.mFlags;
+ mRequiresAuth = builder.mRequiresAuth;
+ mHasCryptoObject = builder.mHasCryptoObject;
+ mCryptoOpId = builder.mCryptoOpId;
}
/** @hide */
@@ -73,13 +82,41 @@
return mExtras;
}
+ /** @hide */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /** @hide */
+ public boolean isAuthRequired() {
+ return mRequiresAuth;
+ }
+
+ /** @hide */
+ public boolean isEmpty() {
+ return mFields.isEmpty();
+ }
+
+ /** @hide */
+ public boolean hasCryptoObject() {
+ return mHasCryptoObject;
+ }
+
+ /** @hide */
+ public long getCryptoObjectOpId() {
+ return mCryptoOpId;
+ }
+
@Override
public String toString() {
if (!DEBUG) return super.toString();
final StringBuilder builder = new StringBuilder("Dataset [name=").append(mName)
.append(", fields=").append(mFields).append(", extras=");
- append(builder, mExtras);
+ append(builder, mExtras)
+ .append(", flags=").append(mFlags)
+ .append(", requiresAuth: ").append(mRequiresAuth)
+ .append(", hasCrypto: ").append(mHasCryptoObject);
return builder.append(']').toString();
}
@@ -90,6 +127,10 @@
private CharSequence mName;
private final ArrayList<DatasetField> mFields = new ArrayList<>();
private Bundle mExtras;
+ private int mFlags;
+ private boolean mRequiresAuth;
+ private boolean mHasCryptoObject;
+ private long mCryptoOpId;
/**
* Creates a new builder.
@@ -103,6 +144,125 @@
}
/**
+ * Requires dataset authentication through the {@link
+ * android.service.autofill.AutoFillService} before auto-filling the activity with this
+ * dataset.
+ *
+ * <p>This method is typically called when the device (or the service) does not support
+ * fingerprint authentication (and hence it cannot use {@link
+ * #requiresFingerprintAuthentication(CryptoObject, Bundle, int)}) or when the service needs
+ * to use a custom authentication UI for the dataset. For example, when a dataset contains
+ * credit card information (such as number, expiration date, and verification code), the
+ * service displays an authentication dialog asking for the verification code to unlock the
+ * rest of the data).
+ *
+ * <p>Since the dataset is "locked" until the user authenticates it, typically this dataset
+ * name is masked (for example, "VISA....1234").
+ *
+ * <p>When the user selects this dataset, the Android System calls {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+ * passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_REQUESTED} in
+ * the flags and the same {@code extras} passed to this method. The service can then
+ * displays its custom authentication UI, and then call the proper method on {@link
+ * android.service.autofill.FillCallback} depending on the authentication result and whether
+ * this dataset already contains the fields needed to auto-fill the activity:
+ *
+ * <ul>
+ * <li>If authentication failed, call
+ * {@link android.service.autofill.FillCallback#onDatasetAuthentication(Dataset,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the flags.
+ * <li>If authentication succeeded and this datast is empty (no fields), call {@link
+ * android.service.autofill.FillCallback#onSuccess(FillResponse)} with a new dataset
+ * (with the proper fields).
+ * <li>If authentication succeeded and this response is not empty, call {@link
+ * android.service.autofill.FillCallback#onDatasetAuthentication(Dataset, int)}
+ * passing
+ * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS} in the
+ * {@code flags} and {@code null} as the {@code dataset}.
+ * </ul>
+ *
+ * @param extras when set, will be passed back in the {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle,
+ * int)}, call so it could be used by the service to handle state.
+ * @param flags optional parameters, currently ignored.
+ */
+ public Builder requiresCustomAuthentication(@Nullable Bundle extras, int flags) {
+ return requiresAuthentication(null, extras, flags);
+ }
+
+ /**
+ * Requires dataset authentication through the Fingerprint sensor before auto-filling the
+ * activity with this dataset.
+ *
+ * <p>This method is typically called when the dataset contains sensitive information (for
+ * example, credit card information) and the provider requires the user to re-authenticate
+ * before using it.
+ *
+ * <p>Since the dataset is "locked" until the user authenticates it, typically this dataset
+ * name is masked (for example, "VISA....1234").
+ *
+ * <p>When the user selects this dataset, the Android System displays an UI affordance
+ * asking the user to use the fingerprint sensor unlock the dataset, and what happens after
+ * a successful fingerprint authentication depends on whether the dataset is empty (no
+ * fields, only the masked name) or not:
+ *
+ * <ul>
+ * <li>If it's empty, the Android System will call {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}} in the
+ * flags.
+ * <li>If it's not empty, the activity will be auto-filled with its data.
+ * </ul>
+ *
+ * <p>If the fingerprint authentication fails, the Android System will call {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+ * passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the
+ * flags.
+ *
+ * <p><strong>NOTE: </note> the {@link android.service.autofill.AutoFillService} should use
+ * the {@link android.hardware.fingerprint.FingerprintManager} to check if fingerpint
+ * authentication is available before using this method, and use other alternatives (such as
+ * {@link #requiresCustomAuthentication(Bundle, int)}) if it is not: if this method is
+ * called when fingerprint is not available, Android System will call {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+ * passing {@link
+ * android.service.autofill.AutoFillService#FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}
+ * in the flags, but it would be wasting system resources (and worsening the user
+ * experience) in the process.
+ *
+ * @param crypto object that will be authenticated.
+ * @param extras when set, will be passed back in the {@link
+ * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+ * call so it could be used by the service to handle state.
+ * @param flags optional parameters, currently ignored.
+ */
+ public Builder requiresFingerprintAuthentication(CryptoObject crypto,
+ @Nullable Bundle extras, int flags) {
+ // TODO(b/33197203): should we allow crypto to be null?
+ Preconditions.checkArgument(crypto != null, "must pass a CryptoObject");
+ return requiresAuthentication(crypto, extras, flags);
+ }
+
+ private Builder requiresAuthentication(CryptoObject cryptoObject, Bundle extras,
+ int flags) {
+ // There can be only one!
+ Preconditions.checkState(!mRequiresAuth,
+ "requires-authentication methods already called");
+ // TODO(b/33197203): make sure that either this method or setExtras() is called, but
+ // not both
+ mExtras = extras;
+ mFlags = flags;
+ mRequiresAuth = true;
+ if (cryptoObject != null) {
+ mHasCryptoObject = true;
+ mCryptoOpId = cryptoObject.getOpId();
+ }
+ return this;
+ }
+
+ /**
* Sets the value of a field.
*
* @param id id returned by {@link ViewNode#getAutoFillId()}.
@@ -121,15 +281,17 @@
}
/**
- * Sets a {@link Bundle} that will be passed to subsequent calls to {@link AutoFillService}
- * methods such as
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
- * {@link AutoFillService#EXTRA_DATASET_EXTRAS} as the key.
+ * Sets a {@link Bundle} that will be passed to subsequent calls to
+ * {@link android.service.autofill.AutoFillService} methods such as
+ * {@link android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
+ * {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} as the key.
*
* <p>It can be used to keep service state in between calls.
*/
public Builder setExtras(Bundle extras) {
+ // TODO(b/33197203): make sure that either this method or the requires-Authentication
+ // ones are called, but not both
mExtras = Objects.requireNonNull(extras, "extras cannot be null");
return this;
}
@@ -158,6 +320,12 @@
parcel.writeCharSequence(mName);
parcel.writeList(mFields);
parcel.writeBundle(mExtras);
+ parcel.writeInt(mFlags);
+ parcel.writeInt(mRequiresAuth ? 1 : 0);
+ parcel.writeInt(mHasCryptoObject ? 1 : 0);
+ if (mHasCryptoObject) {
+ parcel.writeLong(mCryptoOpId);
+ }
}
@SuppressWarnings("unchecked")
@@ -165,6 +333,10 @@
mName = parcel.readCharSequence();
mFields = parcel.readArrayList(null);
mExtras = parcel.readBundle();
+ mFlags = parcel.readInt();
+ mRequiresAuth = parcel.readInt() == 1;
+ mHasCryptoObject = parcel.readInt() == 1;
+ mCryptoOpId = mHasCryptoObject ? parcel.readLong() : 0;
}
public static final Parcelable.Creator<Dataset> CREATOR = new Parcelable.Creator<Dataset>() {
diff --git a/core/java/android/view/autofill/FillResponse.java b/core/java/android/view/autofill/FillResponse.java
index 3a14767..67eb85a 100644
--- a/core/java/android/view/autofill/FillResponse.java
+++ b/core/java/android/view/autofill/FillResponse.java
@@ -18,11 +18,13 @@
import static android.view.autofill.Helper.DEBUG;
import static android.view.autofill.Helper.append;
+import android.annotation.Nullable;
import android.app.Activity;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.service.autofill.AutoFillService;
+import android.service.autofill.FillCallback;
import com.android.internal.util.Preconditions;
@@ -34,14 +36,16 @@
import java.util.Set;
/**
- * Response for a
- * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.FillCallback)}
- * request.
+ * Response for a {@link
+ * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} request.
*
* <p>The response typically contains one or more {@link Dataset}s, each representing a set of
- * fields that can be auto-filled together. For example, for a login page with username/password
- * where the user only have one account in the service, the response could be:
+ * fields that can be auto-filled together, and the Android System displays a dataset picker UI
+ * affordance that the user must use before the {@link Activity} is filled with the dataset.
+ *
+ * <p>For example, for a login page with username/password where the user only has one account in
+ * the service, the response could be:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
@@ -67,9 +71,9 @@
* .build();
* </pre>
*
- * <p>If the user does not have any data associated with this {@link 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 {@code savableIds} instead of {@link Dataset}s:
+ * <p>If the user does not have any data associated with this {@link 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 {@code savableIds} instead of {@link Dataset}s:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
@@ -79,8 +83,8 @@
*
* <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
- * {@code savableIds}:
+ * scenario, the service could populate the response with both {@link Dataset}s and {@code
+ * savableIds}:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
@@ -101,9 +105,9 @@
* <p>If the service has multiple {@link Dataset}s with multiple options for some fields on each
* dataset (for example, multiple accounts with both a home and work address), then it should
* "partition" the {@link Activity} in sections and populate the response with just a subset of the
- * data that would fulfill the first section; then once the user fills the first section and taps
- * a field from the next section, the Android system would issue another request for that section,
- * and so on. For example, the first response could be:
+ * data that would fulfill the first section; then once the user fills the first section and taps a
+ * field from the next section, the Android system would issue another request for that section, and
+ * so on. For example, the first response could be:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
@@ -134,19 +138,31 @@
* .build();
* </pre>
*
- * <p>Finally, the service can use the {@link FillResponse.Builder#setExtras(Bundle)} and/or
- * {@link Dataset.Builder#setExtras(Bundle)} methods to pass
- * a {@link Bundle} with service-specific data use to identify this response on future calls (like
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.SaveCallback)}) - such bundle will be
- * available as the {@link AutoFillService#EXTRA_RESPONSE_EXTRAS} extra in
- * that method's {@code extras} argument.
+ * <p>The service could require user authentication, either at the {@link FillResponse} or {@link
+ * Dataset} levels, prior to auto-filling the activity - see {@link
+ * FillResponse.Builder#requiresFingerprintAuthentication(CryptoObject, Bundle, int)}, {@link
+ * FillResponse.Builder#requiresCustomAuthentication(Bundle, int)}, {@link
+ * Dataset.Builder#requiresFingerprintAuthentication(CryptoObject, Bundle, int)}, and {@link
+ * Dataset.Builder#requiresCustomAuthentication(Bundle, int)} for details.
+ *
+ * <p>Finally, the service can use the {@link FillResponse.Builder#setExtras(Bundle)} and/or {@link
+ * Dataset.Builder#setExtras(Bundle)} methods to pass {@link Bundle}s with service-specific data use
+ * to identify this response on future calls (like {@link
+ * android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback)}) - such bundles
+ * will be available as the {@link android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS}
+ * and {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} extras in that method's
+ * {@code extras} argument.
*/
public final class FillResponse implements Parcelable {
private final List<Dataset> mDatasets;
private final AutoFillId[] mSavableIds;
private final Bundle mExtras;
+ private final int mFlags;
+ private final boolean mRequiresAuth;
+ private final boolean mHasCryptoObject;
+ private final long mCryptoOpId;
private FillResponse(Builder builder) {
// TODO(b/33197203): make it immutable?
@@ -158,6 +174,10 @@
mSavableIds[i++] = id;
}
mExtras = builder.mExtras;
+ mFlags = builder.mFlags;
+ mRequiresAuth = builder.mRequiresAuth;
+ mHasCryptoObject = builder.mHasCryptoObject;
+ mCryptoOpId = builder.mCryptoOpId;
}
/** @hide */
@@ -175,13 +195,161 @@
return mExtras;
}
+ /** @hide */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /** @hide */
+ public boolean isAuthRequired() {
+ return mRequiresAuth;
+ }
+
+ /** @hide */
+ public boolean hasCryptoObject() {
+ return mHasCryptoObject;
+ }
+
+ /** @hide */
+ public long getCryptoObjectOpId() {
+ return mCryptoOpId;
+ }
+
/**
* Builder for {@link FillResponse} objects.
*/
public static final class Builder {
private final List<Dataset> mDatasets = new ArrayList<>();
private final Set<AutoFillId> mSavableIds = new HashSet<>();
- private Bundle mExtras;
+ private Bundle mExtras;
+ private int mFlags;
+ private boolean mRequiresAuth;
+ private boolean mHasCryptoObject;
+ private long mCryptoOpId;
+
+ /**
+ * Requires user authentication through the {@link android.service.autofill.AutoFillService}
+ * before handling an auto-fill request.
+ *
+ * <p>This method is typically called when the device (or the service) does not support
+ * fingerprint authentication (and hence it cannot use {@link
+ * #requiresFingerprintAuthentication(CryptoObject, Bundle, int)}) or when the service needs
+ * to use a custom authentication UI and is used in 2 scenarios:
+ *
+ * <ol>
+ * <li>When the user data is encrypted and the service must authenticate an object that
+ * will be used to decrypt it.
+ * <li>When the service already acquired the user data but wants to confirm the user's
+ * identity before the activity is filled with it.
+ * </ol>
+ *
+ * <p>When this method is called, the Android System displays an UI affordance asking the
+ * user to tap it to auto-fill the activity; if the user taps it, the Android System calls
+ * {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_REQUESTED} in the flags and
+ * the same {@code extras} passed to this method. The service can then displays its custom
+ * authentication UI, and then call the proper method on {@link FillCallback} depending on
+ * the authentication result and whether this response already contains the {@link Dataset}s
+ * need to auto-fill the activity:
+ *
+ * <ul>
+ * <li>If authentication failed, call {@link
+ * FillCallback#onFillResponseAuthentication(int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the flags.
+ * <li>If authentication succeeded and this response is empty (no datasets), call {@link
+ * FillCallback#onSuccess(FillResponse)} with a new dataset (that does not require
+ * authentication).
+ * <li>If authentication succeeded and this response is not empty, call {@link
+ * FillCallback#onFillResponseAuthentication(int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS} in the flags.
+ * </ul>
+ *
+ * @param extras when set, will be passed back in the {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} call so it could be used by the service to handle state.
+ * @param flags optional parameters, currently ignored.
+ */
+ public Builder requiresCustomAuthentication(@Nullable Bundle extras, int flags) {
+ return requiresAuthentication(null, extras, flags);
+ }
+
+ /**
+ * Requires user authentication through the Fingerprint sensor before handling an auto-fill
+ * request.
+ *
+ * <p>The {@link android.service.autofill.AutoFillService} typically uses this method in 2
+ * situations:
+ *
+ * <ol>
+ * <li>When the user data is encrypted and the service must authenticate an object that
+ * will be used to decrypt it.
+ * <li>When the service already acquired the user data but wants to confirm the user's
+ * identity before the activity is filled with it.
+ * </ol>
+ *
+ * <p>When this method is called, the Android System displays an UI affordance asking the
+ * user to use the fingerprint sensor to auto-fill the activity, and what happens after a
+ * successful fingerprint authentication depends on the number of {@link Dataset}s included
+ * in this response:
+ *
+ * <ul>
+ * <li>If it's empty (scenario #1 above), the Android System will call {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}} in the
+ * flags.
+ * <li>If it contains one dataset, the activity will be auto-filled right away.
+ * <li>If it contains many datasets, the Android System will show dataset picker UI, and
+ * then auto-fill the activity once the user select the proper datased.
+ * </ul>
+ *
+ * <p>If the fingerprint authentication fails, the Android System will call {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR}
+ * in the flags.
+ *
+ * <p><strong>NOTE: </note> the {@link android.service.autofill.AutoFillService} should use
+ * the {@link android.hardware.fingerprint.FingerprintManager} to check if fingerpint
+ * authentication is available before using this method, and use other alternatives (such as
+ * {@link #requiresCustomAuthentication(Bundle, int)}) if it is not: if this method is
+ * called when fingerprint is not available, Android System will call {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} passing {@link
+ * android.service.autofill.AutoFillService#FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}
+ * in the flags, but it would be wasting system resources (and worsening the user
+ * experience) in the process.
+ *
+ * @param crypto object that will be authenticated.
+ * @param extras when set, will be passed back in the {@link
+ * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+ * int)} call so it could be used by the service to handle state.
+ * @param flags optional parameters, currently ignored.
+ */
+ public Builder requiresFingerprintAuthentication(CryptoObject crypto,
+ @Nullable Bundle extras, int flags) {
+ // TODO(b/33197203): should we allow crypto to be null?
+ Preconditions.checkArgument(crypto != null, "must pass a CryptoObject");
+ return requiresAuthentication(crypto, extras, flags);
+ }
+
+ private Builder requiresAuthentication(CryptoObject cryptoObject, Bundle extras,
+ int flags) {
+ // There can be only one!
+ Preconditions.checkState(!mRequiresAuth,
+ "requires-authentication methods already called");
+ // TODO(b/33197203): make sure that either this method or setExtras() is called, but
+ // not both
+ mExtras = extras;
+ mFlags = flags;
+ mRequiresAuth = true;
+ if (cryptoObject != null) {
+ mHasCryptoObject = true;
+ mCryptoOpId = cryptoObject.getOpId();
+ }
+ return this;
+ }
/**
* Adds a new {@link Dataset} to this response.
@@ -191,7 +359,6 @@
public Builder addDataset(Dataset dataset) {
Preconditions.checkNotNull(dataset, "dataset cannot be null");
// TODO(b/33197203): check if name already exists
- // TODO(b/33197203): check if authId already exists (and update javadoc)
mDatasets.add(dataset);
for (DatasetField field : dataset.getFields()) {
mSavableIds.add(field.getId());
@@ -201,13 +368,14 @@
/**
* Adds ids of additional fields that the service would be interested to save (through
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.SaveCallback)}) but were not
- * indirectly set through {@link #addDataset(Dataset)}.
+ * {@link android.service.autofill.AutoFillService#onSaveRequest(
+ * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
+ * android.service.autofill.SaveCallback)}) but were not indirectly set through {@link
+ * #addDataset(Dataset)}.
*
* <p>See {@link FillResponse} for examples.
*/
- public Builder addSavableFields(AutoFillId...ids) {
+ public Builder addSavableFields(AutoFillId... ids) {
for (AutoFillId id : ids) {
mSavableIds.add(id);
}
@@ -215,15 +383,18 @@
}
/**
- * Sets a {@link Bundle} that will be passed to subsequent calls to {@link AutoFillService}
- * methods such as
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
- * {@link AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key.
+ * Sets a {@link Bundle} that will be passed to subsequent calls to {@link
+ * android.service.autofill.AutoFillService} methods such as {@link
+ * android.service.autofill.AutoFillService#onSaveRequest(
+ * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
+ * android.service.autofill.SaveCallback)}, using {@link
+ * android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key.
*
* <p>It can be used when to keep service state in between calls.
*/
public Builder setExtras(Bundle extras) {
+ // TODO(b/33197203): make sure that either this method or the requires-Authentication
+ // ones are called, but not both
mExtras = Objects.requireNonNull(extras, "extras cannot be null");
return this;
}
@@ -246,7 +417,10 @@
final StringBuilder builder = new StringBuilder("FillResponse: [datasets=")
.append(mDatasets).append(", savableIds=").append(Arrays.toString(mSavableIds))
.append(", extras=");
- append(builder, mExtras);
+ append(builder, mExtras)
+ .append(", flags=").append(mFlags)
+ .append(", requiresAuth: ").append(mRequiresAuth)
+ .append(", hasCrypto: ").append(mHasCryptoObject);
return builder.append(']').toString();
}
@@ -264,6 +438,12 @@
parcel.writeList(mDatasets);
parcel.writeParcelableArray(mSavableIds, 0);
parcel.writeBundle(mExtras);
+ parcel.writeInt(mFlags);
+ parcel.writeInt(mRequiresAuth ? 1 : 0);
+ parcel.writeInt(mHasCryptoObject ? 1 : 0);
+ if (mHasCryptoObject) {
+ parcel.writeLong(mCryptoOpId);
+ }
}
private FillResponse(Parcel parcel) {
@@ -271,6 +451,10 @@
parcel.readList(mDatasets, null);
mSavableIds = parcel.readParcelableArray(null, AutoFillId.class);
mExtras = parcel.readBundle();
+ mFlags = parcel.readInt();
+ mRequiresAuth = parcel.readInt() == 1;
+ mHasCryptoObject = parcel.readInt() == 1;
+ mCryptoOpId = mHasCryptoObject ? parcel.readLong() : 0;
}
public static final Parcelable.Creator<FillResponse> CREATOR =
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
index 772710e..14cf9e8 100644
--- a/core/java/android/view/autofill/Helper.java
+++ b/core/java/android/view/autofill/Helper.java
@@ -18,28 +18,33 @@
import android.os.Bundle;
+import java.util.Arrays;
+import java.util.Objects;
import java.util.Set;
/** @hide */
public final class Helper {
- // TODO(b/33197203): set to false when stable
- static final boolean DEBUG = true;
+ static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
static final String REDACTED = "[REDACTED]";
- static void append(StringBuilder builder, Bundle bundle) {
+ static StringBuilder append(StringBuilder builder, Bundle bundle) {
if (bundle == null) {
builder.append("N/A");
} else if (!DEBUG) {
builder.append(REDACTED);
} else {
final Set<String> keySet = bundle.keySet();
- builder.append("[bundle with ").append(keySet.size()).append(" extras:");
+ builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
for (String key : keySet) {
- builder.append(' ').append(key).append('=').append(bundle.get(key)).append(',');
+ final Object value = bundle.get(key);
+ builder.append(' ').append(key).append('=');
+ builder.append((value instanceof Object[])
+ ? Arrays.toString((Objects[]) value) : value);
}
builder.append(']');
}
+ return builder;
}
private Helper() {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 00faf65..4071ff4 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -69,14 +69,14 @@
}
@VisibleForTesting
- ResolveInfo getLastChosen() throws RemoteException {
+ public ResolveInfo getLastChosen() throws RemoteException {
return AppGlobals.getPackageManager().getLastChosenActivity(
mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.MATCH_DEFAULT_ONLY);
}
@VisibleForTesting
- void setLastChosen(Intent intent, IntentFilter filter, int match)
+ public void setLastChosen(Intent intent, IntentFilter filter, int match)
throws RemoteException {
AppGlobals.getPackageManager().setLastChosenActivity(intent,
intent.resolveType(mContext.getContentResolver()),
diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java
index 087383d..0fe580a 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/NotificationColorUtil.java
@@ -33,6 +33,8 @@
import android.graphics.drawable.VectorDrawable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import android.util.Pair;
@@ -184,8 +186,24 @@
SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
for (Object span : spans) {
Object resultSpan = span;
- if (span instanceof TextAppearanceSpan) {
- resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span);
+ if (resultSpan instanceof CharacterStyle) {
+ resultSpan = ((CharacterStyle) span).getUnderlying();
+ }
+ if (resultSpan instanceof TextAppearanceSpan) {
+ TextAppearanceSpan processedSpan = processTextAppearanceSpan(
+ (TextAppearanceSpan) span);
+ if (processedSpan != resultSpan) {
+ resultSpan = processedSpan;
+ } else {
+ // we need to still take the orgininal for wrapped spans
+ resultSpan = span;
+ }
+ } else if (resultSpan instanceof ForegroundColorSpan) {
+ ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
+ int foregroundColor = originalSpan.getForegroundColor();
+ resultSpan = new ForegroundColorSpan(processColor(foregroundColor));
+ } else {
+ resultSpan = span;
}
builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
ss.getSpanFlags(span));
@@ -416,6 +434,48 @@
return color;
}
+ public static int resolvePrimaryColor(Context context, int backgroundColor) {
+ boolean useDark = shouldUseDark(backgroundColor);
+ if (useDark) {
+ return context.getColor(
+ com.android.internal.R.color.notification_primary_text_color_light);
+ } else {
+ return context.getColor(
+ com.android.internal.R.color.notification_primary_text_color_dark);
+ }
+ }
+
+ public static int resolveSecondaryColor(Context context, int backgroundColor) {
+ boolean useDark = shouldUseDark(backgroundColor);
+ if (useDark) {
+ return context.getColor(
+ com.android.internal.R.color.notification_secondary_text_color_light);
+ } else {
+ return context.getColor(
+ com.android.internal.R.color.notification_secondary_text_color_dark);
+ }
+ }
+
+ public static int resolveActionBarColor(int backgroundColor) {
+ boolean useDark = shouldUseDark(backgroundColor);
+ final double[] result = ColorUtilsFromCompat.getTempDouble3Array();
+ ColorUtilsFromCompat.colorToLAB(backgroundColor, result);
+ if (useDark && result[0] < 97 || !useDark && result[0] < 4) {
+ result[0] = Math.min(100, result[0] + 7);
+ } else {
+ result[0] = Math.max(0, result[0] - 7);
+ }
+ return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]);
+ }
+
+ private static boolean shouldUseDark(int backgroundColor) {
+ boolean useDark = backgroundColor == Notification.COLOR_DEFAULT;
+ if (!useDark) {
+ useDark = ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5;
+ }
+ return useDark;
+ }
+
/**
* Framework copy of functions needed from android.support.v4.graphics.ColorUtils.
*/
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 094e2b8..1e7baafa 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1609,6 +1609,16 @@
<permission android:name="android.permission.RECEIVE_STK_COMMANDS"
android:protectionLevel="signature|privileged" />
+ <!-- Must be required by an ImsService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature|privileged
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_IMS_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
index bd1eb41..606b686 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
index aedb12c..a4c5bc2 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
index 6f07cb5..9c27833 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
index c41a8ca..6d693f1 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
index ec0a52f..c48fa36 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4k.xml b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
index 78bd0a0..0868845 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_hd.xml b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
index 78085c2f..657f5ed 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_ld.xml b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
index f660ab7..e2971aa 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_sd.xml b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
index 43b8653..b073be3 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="18"
android:viewportHeight="18"
- android:width="18dp"
- android:height="18dp">
+ android:width="26dp"
+ android:height="24dp">
<group
android:translateX="386"
android:translateY="-298">
diff --git a/core/res/res/drawable/ic_wifi_signal_0.xml b/core/res/res/drawable/ic_wifi_signal_0.xml
new file mode 100644
index 0000000..e732a8d
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_0.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_wifi_signal_1.xml b/core/res/res/drawable/ic_wifi_signal_1.xml
new file mode 100644
index 0000000..3d006953
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_1.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.1,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.5,6.5L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0z"/>
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.1,22.0l5.5,-6.8c-0.2,-0.2 -2.3,-1.9 -5.5,-1.9s-5.3,1.8 -5.5,1.9L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_wifi_signal_2.xml b/core/res/res/drawable/ic_wifi_signal_2.xml
new file mode 100644
index 0000000..2cce9e9
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_2.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0l7.6,-9.4C20.3,12.4 17.4,10.0 13.0,10.0s-7.3,2.4 -7.6,2.7L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_wifi_signal_3.xml b/core/res/res/drawable/ic_wifi_signal_3.xml
new file mode 100644
index 0000000..d3b3d3a
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_3.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0l9.2,-11.4c-0.4,-0.3 -3.9,-3.2 -9.2,-3.2s-8.9,3.0 -9.2,3.2L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_wifi_signal_4.xml b/core/res/res/drawable/ic_wifi_signal_4.xml
new file mode 100644
index 0000000..aca4551
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_4.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 4164e5d..b28c6f2 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -89,7 +89,7 @@
<color name="perms_dangerous_perm_color">#33b5e5</color>
<color name="shadow">#cc222222</color>
<color name="perms_costs_money">#fff4511e</color>
-
+
<!-- For search-related UIs -->
<color name="search_url_text_normal">#7fa87f</color>
<color name="search_url_text_selected">@android:color/black</color>
@@ -132,6 +132,10 @@
<drawable name="notification_template_icon_low_bg">#0cffffff</drawable>
<drawable name="notification_template_divider">#29000000</drawable>
<drawable name="notification_template_divider_media">#29ffffff</drawable>
+ <color name="notification_primary_text_color_light">@color/primary_text_default_material_light</color>
+ <color name="notification_primary_text_color_dark">@color/primary_text_default_material_dark</color>
+ <color name="notification_secondary_text_color_light">@color/secondary_text_material_light</color>
+ <color name="notification_secondary_text_color_dark">@color/secondary_text_material_dark</color>
<color name="notification_material_background_color">#ffffffff</color>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 0b326e9..1e15348 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -451,14 +451,14 @@
</style>
<style name="TextAppearance.Material.Notification">
- <item name="textColor">@color/secondary_text_material_light</item>
+ <item name="textColor">@color/notification_secondary_text_color_light</item>
<item name="textSize">@dimen/notification_text_size</item>
</style>
<style name="TextAppearance.Material.Notification.Reply" />
<style name="TextAppearance.Material.Notification.Title">
- <item name="textColor">@color/primary_text_default_material_light</item>
+ <item name="textColor">@color/notification_primary_text_color_light</item>
<item name="textSize">@dimen/notification_title_text_size</item>
</style>
@@ -467,7 +467,7 @@
</style>
<style name="TextAppearance.Material.Notification.Info">
- <item name="textColor">@color/secondary_text_default_material_light</item>
+ <item name="textColor">@color/notification_secondary_text_color_light</item>
<item name="textSize">@dimen/notification_subtext_size</item>
</style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 78f6b49..a732998 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1256,6 +1256,11 @@
<java-symbol type="drawable" name="platlogo" />
<java-symbol type="drawable" name="stat_notify_sync_error" />
<java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+ <java-symbol type="drawable" name="ic_wifi_signal_0" />
+ <java-symbol type="drawable" name="ic_wifi_signal_1" />
+ <java-symbol type="drawable" name="ic_wifi_signal_2" />
+ <java-symbol type="drawable" name="ic_wifi_signal_3" />
+ <java-symbol type="drawable" name="ic_wifi_signal_4" />
<java-symbol type="drawable" name="ic_signal_wifi_badged_0_bars" />
<java-symbol type="drawable" name="ic_signal_wifi_badged_1_bar" />
<java-symbol type="drawable" name="ic_signal_wifi_badged_2_bars" />
@@ -2791,6 +2796,11 @@
<java-symbol type="string" name="notification_header_divider_symbol_with_spaces" />
<java-symbol type="string" name="config_defaultCellBroadcastReceiverComponent" />
+ <java-symbol type="color" name="notification_primary_text_color_light" />
+ <java-symbol type="color" name="notification_primary_text_color_dark" />
+ <java-symbol type="color" name="notification_secondary_text_color_light" />
+ <java-symbol type="color" name="notification_secondary_text_color_dark" />
+
<java-symbol type="string" name="app_category_game" />
<java-symbol type="string" name="app_category_audio" />
<java-symbol type="string" name="app_category_video" />
diff --git a/packages/SettingsLib/src/com/android/settingslib/TronUtils.java b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
new file mode 100644
index 0000000..1d9d03a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
@@ -0,0 +1,63 @@
+/*
+ * 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.settingslib;
+
+import android.content.Context;
+import android.net.ScoredNetwork;
+
+import com.android.internal.logging.MetricsLogger;
+
+/** Utilites for Tron Logging. */
+public final class TronUtils {
+
+ private TronUtils() {};
+
+ public static void logWifiSettingsBadge(Context context, int badgeEnum) {
+ logNetworkBadgeMetric(context, "settings_wifibadging", badgeEnum);
+ }
+
+ /**
+ * Logs an occurrence of the given network badge to a Histogram.
+ *
+ * @param context Context
+ * @param histogram the Tron histogram name to write to
+ * @param badgeEnum the {@link ScoredNetwork.Badging} badge value
+ * @throws IllegalArgumentException if the given badge enum is not supported
+ */
+ private static void logNetworkBadgeMetric(
+ Context context, String histogram, int badgeEnum)
+ throws IllegalArgumentException {
+ int bucket;
+ switch (badgeEnum) {
+ case ScoredNetwork.BADGING_NONE:
+ bucket = 0;
+ break;
+ case ScoredNetwork.BADGING_SD:
+ bucket = 1;
+ break;
+ case ScoredNetwork.BADGING_HD:
+ bucket = 2;
+ break;
+ case ScoredNetwork.BADGING_4K:
+ bucket = 3;
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported badge enum: " + badgeEnum);
+ }
+
+ MetricsLogger.histogram(context, histogram, bucket);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 8ebea61..703bc17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -34,7 +34,9 @@
import android.util.SparseArray;
import android.widget.ImageView;
import android.widget.TextView;
+
import com.android.settingslib.R;
+import com.android.settingslib.TronUtils;
import com.android.settingslib.Utils;
public class AccessPointPreference extends Preference {
@@ -184,7 +186,8 @@
if (level == -1) {
safeSetDefaultIcon();
} else {
- if (mWifiBadge != ScoredNetwork.BADGING_NONE) {
+ TronUtils.logWifiSettingsBadge(context, mWifiBadge);
+ if (mWifiBadge != ScoredNetwork.BADGING_NONE) {
// TODO(sghuman): Refactor this to reuse drawable to save memory and add to a
// special subclass of AccessPointPreference
LayerDrawable drawable = Utils.getBadgedWifiIcon(context, level, mWifiBadge);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
new file mode 100644
index 0000000..c0a48a8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
@@ -0,0 +1,74 @@
+
+package com.android.systemui.plugins.statusbar;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+import java.util.ArrayList;
+
+import com.android.systemui.plugins.Plugin;
+
+public interface NotificationMenuRowProvider extends Plugin {
+
+ public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
+
+ public static final int VERSION = 1;
+
+ /**
+ * Returns a list of items to populate the menu 'behind' a notification.
+ */
+ public ArrayList<MenuItem> getMenuItems(Context context);
+
+ public interface OnMenuClickListener {
+ public void onMenuClicked(View row, int x, int y, MenuItem menu);
+
+ public void onMenuReset(View row);
+ }
+
+ public interface GutsInteractionListener {
+ public void onInteraction(View view);
+
+ public void closeGuts(View view);
+ }
+
+ public interface GutsContent {
+ public void setInteractionListener(GutsInteractionListener listener);
+
+ public View getContentView();
+
+ public boolean handleCloseControls();
+ }
+
+ public interface SnoozeGutsContent extends GutsContent {
+ public void setSnoozeListener(SnoozeListener listener);
+
+ public void setStatusBarNotification(StatusBarNotification sbn);
+ }
+
+ public interface SnoozeListener {
+ public void snoozeNotification(StatusBarNotification sbn, long snoozeUntil);
+ }
+
+ public static class MenuItem {
+ public Drawable icon;
+ public String menuDescription;
+ public View menuView;
+ public GutsContent gutsContent;
+
+ public MenuItem(Drawable i, String s, GutsContent content) {
+ icon = i;
+ menuDescription = s;
+ gutsContent = content;
+ }
+
+ public View getGutsView() {
+ return gutsContent.getContentView();
+ }
+
+ public boolean onTouch(View v, int x, int y) {
+ return false;
+ }
+ }
+}
diff --git a/packages/SystemUI/res/drawable/ic_left.xml b/packages/SystemUI/res/drawable/ic_left.xml
new file mode 100644
index 0000000..cea4cfc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_left.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.41,16.09l-4.58,-4.59 4.58,-4.59L14.0,5.5l-6.0,6.0 6.0,6.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_menu.xml b/packages/SystemUI/res/drawable/ic_menu.xml
new file mode 100644
index 0000000..994bc5e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_menu.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.0,18.0l18.0,0.0l0.0,-2.0L3.0,16.0l0.0,2.0zm0.0,-5.0l18.0,0.0l0.0,-2.0L3.0,11.0l0.0,2.0zm0.0,-7.0l0.0,2.0l18.0,0.0L21.0,6.0L3.0,6.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_remove.xml b/packages/SystemUI/res/drawable/ic_remove.xml
new file mode 100644
index 0000000..75b2793
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_remove.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.0,13.0L5.0,13.0l0.0,-2.0l14.0,0.0l0.0,2.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_right.xml b/packages/SystemUI/res/drawable/ic_right.xml
new file mode 100644
index 0000000..35699f73
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_right.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10.0,5.75l6.0,6.0 -6.0,6.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml
new file mode 100644
index 0000000..b0b03a9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_snooze.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
+ android:fillColor="#757575"/>
+ <path
+ android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
+ android:fillColor="#757575"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index 3948dc4..9d8ef83 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -23,125 +23,4 @@
android:visibility="gone"
android:clickable="true"
android:gravity="top|start"
- android:orientation="vertical"
- android:paddingStart="@*android:dimen/notification_content_margin_start"
- android:paddingEnd="8dp"
- android:background="@color/notification_guts_bg_color"
- android:theme="@*android:style/Theme.DeviceDefault.Light">
-
- <!-- header -->
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="20dp"
- android:paddingEnd="8dp"
- android:paddingBottom="15dp"
- android:id="@+id/notification_guts_header">
- <TextView
- android:id="@+id/pkgname"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- style="@style/TextAppearance.NotificationGuts.Secondary" />
- <TextView
- android:id="@+id/channel_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/pkgname"
- style="@style/TextAppearance.NotificationGuts.Header" />
- <Switch
- android:id="@+id/channel_enabled_switch"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:background="@null" />
- </RelativeLayout>
- <!-- Importance radio buttons -->
- <LinearLayout
- android:id="@+id/importance"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
- <RadioGroup
- android:id="@+id/importance_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingEnd="@*android:dimen/notification_content_margin_end">
- <RadioButton
- android:id="@+id/high_importance"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_inline_importance_height"
- style="@style/TextAppearance.NotificationGuts.Radio"
- android:buttonTint="@color/notification_guts_buttons" />
- <RadioButton
- android:id="@+id/default_importance"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_inline_importance_height"
- style="@style/TextAppearance.NotificationGuts.Radio"
- android:buttonTint="@color/notification_guts_buttons" />
- <RadioButton
- android:id="@+id/low_importance"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_inline_importance_height"
- style="@style/TextAppearance.NotificationGuts.Radio"
- android:buttonTint="@color/notification_guts_buttons" />
- <RadioButton
- android:id="@+id/min_importance"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_inline_importance_height"
- style="@style/TextAppearance.NotificationGuts.Radio"
- android:buttonTint="@color/notification_guts_buttons" />
- </RadioGroup>
- <LinearLayout
- android:id="@+id/importance_buttons_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <include layout="@layout/notification_guts_importance_text"/>
- <include layout="@layout/notification_guts_importance_text"/>
- <include layout="@layout/notification_guts_importance_text"/>
- <include layout="@layout/notification_guts_importance_text"/>
- </LinearLayout>
- </LinearLayout>
- <!-- Channel Disabled Text -->
- <TextView
- android:id="@+id/channel_disabled"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/notification_channel_disabled"
- style="@style/TextAppearance.NotificationGuts.Secondary" />
- <!-- Settings and Done buttons -->
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="end"
- android:paddingTop="16dp"
- android:paddingBottom="8dp" >
-
- <TextView
- android:id="@+id/more_settings"
- android:text="@string/notification_more_settings"
- android:layout_width="wrap_content"
- android:layout_height="36dp"
- style="@style/TextAppearance.NotificationGuts.Button"
- android:background="@drawable/btn_borderless_rect"
- android:gravity="center"
- android:paddingEnd="8dp"
- android:paddingStart="8dp"
- android:focusable="true" />
-
- <TextView
- android:id="@+id/done"
- android:text="@string/notification_done"
- android:layout_width="wrap_content"
- android:layout_height="36dp"
- style="@style/TextAppearance.NotificationGuts.Button"
- android:background="@drawable/btn_borderless_rect"
- android:gravity="center"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:focusable="true"/>
- </LinearLayout>
-</com.android.systemui.statusbar.NotificationGuts>
+ android:theme="@*android:style/Theme.DeviceDefault.Light"/>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
new file mode 100644
index 0000000..9770ecc
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2017, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.systemui.statusbar.NotificationInfo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/notification_guts"
+ android:clickable="true"
+ android:gravity="top|start"
+ android:orientation="vertical"
+ android:paddingStart="@*android:dimen/notification_content_margin_start"
+ android:paddingEnd="8dp"
+ android:background="@color/notification_guts_bg_color"
+ android:theme="@*android:style/Theme.DeviceDefault.Light">
+
+ <!-- header -->
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="20dp"
+ android:paddingEnd="8dp"
+ android:paddingBottom="15dp"
+ android:id="@+id/notification_guts_header">
+ <TextView
+ android:id="@+id/pkgname"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ style="@style/TextAppearance.NotificationGuts.Secondary" />
+ <TextView
+ android:id="@+id/channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/pkgname"
+ style="@style/TextAppearance.NotificationGuts.Header" />
+ <Switch
+ android:id="@+id/channel_enabled_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:background="@null" />
+ </RelativeLayout>
+ <!-- Importance radio buttons -->
+ <LinearLayout
+ android:id="@+id/importance"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <RadioGroup
+ android:id="@+id/importance_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="@*android:dimen/notification_content_margin_end">
+ <RadioButton
+ android:id="@+id/high_importance"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ style="@style/TextAppearance.NotificationGuts.Radio"
+ android:buttonTint="@color/notification_guts_buttons" />
+ <RadioButton
+ android:id="@+id/default_importance"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ style="@style/TextAppearance.NotificationGuts.Radio"
+ android:buttonTint="@color/notification_guts_buttons" />
+ <RadioButton
+ android:id="@+id/low_importance"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ style="@style/TextAppearance.NotificationGuts.Radio"
+ android:buttonTint="@color/notification_guts_buttons" />
+ <RadioButton
+ android:id="@+id/min_importance"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_inline_importance_height"
+ style="@style/TextAppearance.NotificationGuts.Radio"
+ android:buttonTint="@color/notification_guts_buttons" />
+ </RadioGroup>
+ <LinearLayout
+ android:id="@+id/importance_buttons_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <include layout="@layout/notification_guts_importance_text"/>
+ <include layout="@layout/notification_guts_importance_text"/>
+ <include layout="@layout/notification_guts_importance_text"/>
+ <include layout="@layout/notification_guts_importance_text"/>
+ </LinearLayout>
+ </LinearLayout>
+ <!-- Channel Disabled Text -->
+ <TextView
+ android:id="@+id/channel_disabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/notification_channel_disabled"
+ style="@style/TextAppearance.NotificationGuts.Secondary" />
+ <!-- Settings and Done buttons -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp" >
+
+ <TextView
+ android:id="@+id/more_settings"
+ android:text="@string/notification_more_settings"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ style="@style/TextAppearance.NotificationGuts.Button"
+ android:background="@drawable/btn_borderless_rect"
+ android:gravity="center"
+ android:paddingEnd="8dp"
+ android:paddingStart="8dp"
+ android:focusable="true" />
+
+ <TextView
+ android:id="@+id/done"
+ android:text="@string/notification_done"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ style="@style/TextAppearance.NotificationGuts.Button"
+ android:background="@drawable/btn_borderless_rect"
+ android:gravity="center"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
+ android:focusable="true"/>
+ </LinearLayout>
+</com.android.systemui.statusbar.NotificationInfo>
diff --git a/packages/SystemUI/res/layout/notification_menu_row.xml b/packages/SystemUI/res/layout/notification_menu_row.xml
new file mode 100644
index 0000000..12bcf81
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_menu_row.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2016, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.systemui.statusbar.NotificationMenuRow
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_settings_icon_row.xml b/packages/SystemUI/res/layout/notification_settings_icon_row.xml
deleted file mode 100644
index da3461b90..0000000
--- a/packages/SystemUI/res/layout/notification_settings_icon_row.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2016, The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.systemui.statusbar.NotificationSettingsIconRow
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="invisible"
- >
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/gear_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="@dimen/notification_gear_padding"
- android:src="@drawable/ic_settings"
- android:tint="@color/notification_gear_color"
- android:alpha="0"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- />
-
-</com.android.systemui.statusbar.NotificationSettingsIconRow>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
new file mode 100644
index 0000000..5bd64de
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2017, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.systemui.statusbar.NotificationSnooze
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/snooze_snackbar_min_height"
+ android:id="@+id/notification_snooze"
+ android:clickable="true"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:background="@color/snooze_snackbar_bg">
+
+ <TextView
+ android:id="@+id/snooze_option_default"
+ style="@style/TextAppearance.SnoozeSnackBar"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:drawableTint="@android:color/white"
+ android:drawableEnd="@drawable/notification_expand_more"/>
+
+ <android.widget.Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ />
+
+ <TextView
+ android:id="@+id/undo"
+ style="@style/TextAppearance.SnoozeSnackBar.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="8dp"
+ android:layout_marginStart="8dp"
+ android:background="@drawable/btn_borderless_rect"
+ android:layout_gravity="end"
+ android:text="@string/snooze_undo" />
+
+ <LinearLayout
+ android:id="@+id/snooze_options"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:orientation="vertical"/>
+
+</com.android.systemui.statusbar.NotificationSnooze>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index e456984..d62cc18 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -24,10 +24,10 @@
>
<ViewStub
- android:layout="@layout/notification_settings_icon_row"
- android:id="@+id/settings_icon_row_stub"
- android:inflatedId="@+id/notification_settings_icon_row"
- android:layout_width="wrap_content"
+ android:layout="@layout/notification_menu_row"
+ android:id="@+id/menu_row_stub"
+ android:inflatedId="@+id/notification_menu_row"
+ android:layout_width="match_parent"
android:layout_height="match_parent"
/>
diff --git a/packages/SystemUI/res/layout/tuner_shortcut_item.xml b/packages/SystemUI/res/layout/tuner_shortcut_item.xml
new file mode 100644
index 0000000..e9eae3b
--- /dev/null
+++ b/packages/SystemUI/res/layout/tuner_shortcut_item.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ android:clickable="true"
+ android:gravity="center"
+ android:background="?android:attr/selectableItemBackground">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp" />
+
+ <TextView android:id="@android:id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorPrimary" />
+
+ <com.android.systemui.statusbar.phone.ExpandableIndicator
+ android:id="@+id/expand"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp"
+ android:visibility="gone" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tuner_shortcut_list.xml b/packages/SystemUI/res/layout/tuner_shortcut_list.xml
new file mode 100644
index 0000000..9aaffb4
--- /dev/null
+++ b/packages/SystemUI/res/layout/tuner_shortcut_list.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<android.support.v7.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="200dp"
+ android:gravity="center" />
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index fd147cd..63b9d75 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -34,7 +34,7 @@
<bool name="config_keyguardUserSwitcher">true</bool>
<!-- Nav bar button default ordering/layout -->
- <string name="config_navBarLayout" translatable="false">space;back,home,recent;menu_ime</string>
+ <string name="config_navBarLayout" translatable="false">left;back,home,recent;right</string>
<!-- Animation duration when using long press on recents to dock -->
<integer name="long_press_dock_anim_duration">290</integer>
diff --git a/packages/SystemUI/res/values-sw900dp/config.xml b/packages/SystemUI/res/values-sw900dp/config.xml
index d8f9ef4..221b013 100644
--- a/packages/SystemUI/res/values-sw900dp/config.xml
+++ b/packages/SystemUI/res/values-sw900dp/config.xml
@@ -19,6 +19,6 @@
<resources>
<!-- Nav bar button default ordering/layout -->
- <string name="config_navBarLayout" translatable="false">back,home;space;menu_ime,recent</string>
+ <string name="config_navBarLayout" translatable="false">back,home,left;space;right,recent</string>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3040814..1ec611a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -103,6 +103,10 @@
<color name="notification_guts_icon_tint">#8a000000</color>
<color name="notification_guts_disabled_icon_tint">#4d000000</color>
+ <!-- Colors of the snooze menu reached via snooze icon behind a notification -->
+ <color name="snooze_snackbar_bg">#FF4A4A4A</color>
+ <color name="snooze_snackbar_text">#FFA6BAFF</color>
+
<color name="assist_orb_color">#ffffff</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d6ed9d8..b7647cf 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -286,7 +286,7 @@
<string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
<!-- Nav bar button default ordering/layout -->
- <string name="config_navBarLayout" translatable="false">space,back;home;recent,menu_ime</string>
+ <string name="config_navBarLayout" translatable="false">left,back;home;recent,right</string>
<bool name="quick_settings_show_full_alarm">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 79529a7..ddcc4ba 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -106,11 +106,20 @@
<!-- Minimum layouted height of a notification in the statusbar-->
<dimen name="min_notification_layout_height">48dp</dimen>
- <!-- Width of the space containing the gear icon behind a notification -->
- <dimen name="notification_gear_width">64dp</dimen>
+ <!-- Size of the space to place a notification menu item -->
+ <dimen name="notification_menu_icon_size">64dp</dimen>
- <!-- The space around the gear icon displayed behind a notification -->
- <dimen name="notification_gear_padding">20dp</dimen>
+ <!-- The space around a notification menu item -->
+ <dimen name="notification_menu_icon_padding">20dp</dimen>
+
+ <!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
+ <dimen name="snooze_snackbar_min_height">48dp</dimen>
+
+ <!-- The text size of options in the snooze menu. -->
+ <dimen name="snooze_option_text_size">14sp</dimen>
+
+ <!-- The padding around options int the snooze menu. -->
+ <dimen name="snooze_option_padding">8dp</dimen>
<!-- size at which Notification icons will be drawn in the status bar -->
<dimen name="status_bar_icon_drawing_size">17dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 4a19dde..fc0c82c 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -58,7 +58,6 @@
<item type="id" name="transformation_start_y_tag"/>
<item type="id" name="transformation_start_scale_x_tag"/>
<item type="id" name="transformation_start_scale_y_tag"/>
- <item type="id" name="custom_background_color"/>
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 422431e..ab27d07 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1375,8 +1375,26 @@
<!-- Notification: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
<string name="notification_done">Done</string>
- <!-- Notification: Gear: Content description for the gear. [CHAR LIMIT=NONE] -->
- <string name="notification_gear_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> notification controls</string>
+ <!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
+ <string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
+
+ <!-- Notification: Menu row: Content description for the gear menu item. [CHAR LIMIT=NONE] -->
+ <string name="notification_menu_gear_description">notification controls</string>
+
+ <!-- Notification: Menu row: Content description for the snooze icon. [CHAR LIMIT=NONE] -->
+ <string name="notification_menu_snooze_description">notification snooze options</string>
+
+ <!-- Notification: Menu row: Snooze options: 15 minute option. [CHAR LIMIT=50]-->
+ <string name="snooze_option_15_min">15 minutes</string>
+ <!-- Notification: Menu row: Snooze options: 30 minute option. [CHAR LIMIT=50]-->
+ <string name="snooze_option_30_min">30 minutes</string>
+ <!-- Notification: Menu row: Snooze options: 1 hour option. [CHAR LIMIT=50]-->
+ <string name="snooze_option_1_hour">1 hour</string>
+ <!-- Notification: Menu row: Snooze undo button label. [CHAR LIMIT=50]-->
+ <string name="snooze_undo">UNDO</string>
+
+ <!-- Notification: Menu row: Snooze: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
+ <string name="snoozed_for_time">Snoozed for <xliff:g id="time_amount" example="15 minutes">%1$s</xliff:g></string>
<!-- Title of the battery settings detail panel [CHAR LIMIT=20] -->
<string name="battery_panel_title">Battery usage</string>
@@ -1523,56 +1541,71 @@
<!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
<string name="nav_bar">Navigation bar</string>
- <!-- SysUI Tuner: Group of buttons that show on the start of the screen [CHAR LIMIT=30] -->
- <string name="start">Start</string>
- <!-- SysUI Tuner: Group of buttons that show on the center of the screen [CHAR LIMIT=30] -->
- <string name="center">Center</string>
- <!-- SysUI Tuner: Group of buttons that show on the end of the screen [CHAR LIMIT=30] -->
- <string name="end">End</string>
- <!-- SysUI Tuner: Name of space used in custom navigation bar layouts [CHAR LIMIT=30] -->
- <string name="space">Spacer</string>
+ <!-- SysUI Tuner: Button that controls layout of navigation bar [CHAR LIMIT=60] -->
+ <string name="nav_bar_layout">Layout</string>
+
+ <!-- SysUI Tuner: Label for section of settings about the left nav button [CHAR LIMIT=60] -->
+ <string name="nav_bar_left">Left</string>
+
+ <!-- SysUI Tuner: Label for section of settings about the right nav button [CHAR LIMIT=60] -->
+ <string name="nav_bar_right">Right</string>
+
+ <!-- SysUI Tuner: Setting for button type in nav bar [CHAR LIMIT=60] -->
+ <string name="nav_bar_button_type">Button type</string>
+
+ <!-- SysUI Tuner: Added to nav bar option to indicate it is the default [CHAR LIMIT=60] -->
+ <string name="nav_bar_default"> (default)</string>
+
+ <!-- SysUI Tuner: Labels for different types of navigation bar buttons [CHAR LIMIT=60] -->
+ <string-array name="nav_bar_buttons">
+ <item>Clipboard</item>
+ <item>Keycode</item>
+ <item>Menu / Keyboard Switcher</item>
+ <item>None</item>
+ </string-array>
+ <string-array name="nav_bar_button_values" translatable="false">
+ <item>clipboard</item>
+ <item>key</item>
+ <item>menu_ime</item>
+ <item>space</item>
+ </string-array>
+
+ <!-- SysUI Tuner: Labels for different types of navigation bar layouts [CHAR LIMIT=60] -->
+ <string-array name="nav_bar_layouts">
+ <item>Divided (default)</item>
+ <item>Centered</item>
+ <item>Left-aligned</item>
+ <item>Right-aligned</item>
+ </string-array>
+
+ <string-array name="nav_bar_layouts_values" translatable="false">
+ <item>default</item>
+ <item>left;back,home,recent;right</item>
+ <item>left,back,home,recent,right;space;space</item>
+ <item>space;space;left,back,home,recent,right</item>
+ </string-array>
+
<!-- SysUI Tuner: Name of Combination Menu / Keyboard Switcher button [CHAR LIMIT=30] -->
<string name="menu_ime">Menu / Keyboard Switcher</string>
- <!-- SysUI Tuner: Title for dialog to add a button [CHAR LIMIT=30] -->
- <string name="select_button">Select button to add</string>
- <!-- SysUI Tuner: Button to add a button [CHAR LIMIT=30] -->
- <string name="add_button">Add button</string>
<!-- SysUI Tuner: Save the current settings [CHAR LIMIT=30] -->
<string name="save">Save</string>
<!-- SysUI Tuner: Reset to default settings [CHAR LIMIT=30] -->
<string name="reset">Reset</string>
- <!-- SysUI Tuner: Title of no home warning dialog [CHAR LIMIT=30] -->
- <string name="no_home_title">No home button found</string>
- <!-- SysUI Tuner: Message of no home warning dialog [CHAR LIMIT=NONE] -->
- <string name="no_home_message">A home button is required to be able to navigate this device. Please add a home button before saving.</string>
-
<!-- SysUI Tuner: Adjust button width dialog title [CHAR LIMIT=60] -->
<string name="adjust_button_width">Adjust button width</string>
<!-- SysUI Tuner: Nav bar button that holds the clipboard [CHAR LIMIT=30] -->
<string name="clipboard">Clipboard</string>
- <!-- SysUI Tuner: Description of nav bar button that holds the clipboard [CHAR LIMIT=NONE] -->
- <string name="clipboard_description">The Clipboard allows items to be dragged directly to the clipboard. Items can also be dragged directly out of the clipboard when present.</string>
-
<!-- SysUI Tuner: Accessibility description for custom nav key [CHAR LIMIT=NONE] -->
<string name="accessibility_key">Custom navigation button</string>
<!-- SysUI Tuner: Nav bar button that emulates a keycode [CHAR LIMIT=30] -->
<string name="keycode">Keycode</string>
- <!-- SysUI Tuner: Description of nav bar button that emulates a keycode [CHAR LIMIT=NONE] -->
- <string name="keycode_description">Keycode buttons allow keyboard keys to
- be added to the Navigation Bar. When pressed they emulate the selected
- keyboard key. First the key must be selected for the button, followed
- by an image to be shown on the button.</string>
-
- <!-- SysUI Tuner: Title of dialog to select which key to emulate [CHAR LIMIT=60] -->
- <string name="select_keycode">Select Keyboard Button</string>
-
- <!-- SysUI Tuner: Label for preview area in navigation bar tuner [CHAR LIMIT=NONE] -->
- <string name="preview">Preview</string>
+ <!-- SysUI Tuner: Settings to change nav bar icon [CHAR LIMIT=30] -->
+ <string name="icon">Icon</string>
<!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
<string name="drag_to_add_tiles">Drag to add tiles</string>
@@ -1752,5 +1785,19 @@
<!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] -->
<string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string>
+ <!-- SysUI Tuner: Group of settings for left lock screen affordance [CHAR LIMIT=60] -->
+ <string name="lockscreen_left">Left</string>
+
+ <!-- SysUI Tuner: Group of settings for right lock screen affordance [CHAR LIMIT=60] -->
+ <string name="lockscreen_right">Right</string>
+
+ <!-- SysUI Tuner: Switch controlling whether to customize lock screen button [CHAR LIMIT=60] -->
+ <string name="lockscreen_customize">Customize shortcut</string>
+
+ <!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] -->
+ <string name="lockscreen_shortcut">Shortcut</string>
+
+ <!-- SysUI Tuner: Switch to control if device gets unlocked [CHAR LIMIT=60] -->
+ <string name="lockscreen_unlock">Prompt for password</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6535a81..c5a5518 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -383,6 +383,20 @@
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
+ <style name="TextAppearance.SnoozeSnackBar">
+ <item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">roboto-regular</item>
+ <item name="android:textColor">@android:color/white</item>
+ </style>
+
+ <style name="TextAppearance.SnoozeSnackBar.Button">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:fontFamily">sans-serif-medium</item>
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">@color/snooze_snackbar_text</item>
+ </style>
+
<style name="edit_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
<item name="android:colorBackground">?android:attr/colorSecondary</item>
</style>
diff --git a/packages/SystemUI/res/xml/lockscreen_settings.xml b/packages/SystemUI/res/xml/lockscreen_settings.xml
new file mode 100644
index 0000000..73e29af
--- /dev/null
+++ b/packages/SystemUI/res/xml/lockscreen_settings.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:title="@string/other">
+
+ <PreferenceCategory
+ android:key="left"
+ android:title="@string/lockscreen_left">
+
+ <SwitchPreference
+ android:key="customize"
+ android:title="@string/lockscreen_customize" />
+
+ <Preference
+ android:key="shortcut"
+ android:title="@string/lockscreen_shortcut" />
+
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="sysui_keyguard_left_unlock"
+ android:title="@string/lockscreen_unlock"
+ sysui:defValue="true" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="right"
+ android:title="@string/lockscreen_right">
+
+ <SwitchPreference
+ android:key="customize"
+ android:title="@string/lockscreen_customize" />
+
+ <Preference
+ android:key="shortcut"
+ android:title="@string/lockscreen_shortcut" />
+
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="sysui_keyguard_right_unlock"
+ android:title="@string/lockscreen_unlock"
+ sysui:defValue="true" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/nav_bar_tuner.xml b/packages/SystemUI/res/xml/nav_bar_tuner.xml
new file mode 100644
index 0000000..6fa8bec
--- /dev/null
+++ b/packages/SystemUI/res/xml/nav_bar_tuner.xml
@@ -0,0 +1,79 @@
+<?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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:title="@string/nav_bar">
+
+ <ListPreference
+ android:key="layout"
+ android:title="@string/nav_bar_layout"
+ android:summary="%s"
+ android:persistent="false"
+ android:entries="@array/nav_bar_layouts"
+ android:entryValues="@array/nav_bar_layouts_values" />
+
+ <PreferenceCategory
+ android:key="left"
+ android:title="@string/nav_bar_left">
+
+ <DropDownPreference
+ android:key="type_left"
+ android:title="@string/nav_bar_button_type"
+ android:persistent="false"
+ android:summary="%s"
+ android:entries="@array/nav_bar_buttons"
+ android:entryValues="@array/nav_bar_button_values" />
+
+ <Preference
+ android:key="keycode_left"
+ android:persistent="false"
+ android:title="@string/keycode" />
+
+ <com.android.systemui.tuner.BetterListPreference
+ android:key="icon_left"
+ android:persistent="false"
+ android:summary="%s"
+ android:title="@string/icon" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="right"
+ android:title="@string/nav_bar_right">
+
+ <DropDownPreference
+ android:key="type_right"
+ android:title="@string/nav_bar_button_type"
+ android:summary="%s"
+ android:persistent="false"
+ android:entries="@array/nav_bar_buttons"
+ android:entryValues="@array/nav_bar_button_values" />
+
+ <Preference
+ android:key="keycode_right"
+ android:persistent="false"
+ android:title="@string/keycode" />
+
+ <com.android.systemui.tuner.BetterListPreference
+ android:key="icon_right"
+ android:persistent="false"
+ android:summary="%s"
+ android:title="@string/icon" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 59a10da..94a7c07 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -150,12 +150,15 @@
</PreferenceScreen>
- <!--
<Preference
android:key="nav_bar"
android:title="@string/nav_bar"
android:fragment="com.android.systemui.tuner.NavBarTuner" />
- -->
+
+ <Preference
+ android:key="lockscreen"
+ android:title="@string/accessibility_desc_lock_screen"
+ android:fragment="com.android.systemui.tuner.LockscreenFragment" />
<Preference
android:key="other"
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 5c50b5c..a95713f 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -32,7 +32,9 @@
import android.view.accessibility.AccessibilityEvent;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.NotificationMenuRow;
import java.util.HashMap;
@@ -85,10 +87,12 @@
private int mFalsingThreshold;
private boolean mTouchAboveFalsingThreshold;
private boolean mDisableHwLayers;
+ private Context mContext;
private HashMap<View, Animator> mDismissPendingMap = new HashMap<>();
public SwipeHelper(int swipeDirection, Callback callback, Context context) {
+ mContext = context;
mCallback = callback;
mHandler = new Handler();
mSwipeDirection = swipeDirection;
@@ -249,6 +253,7 @@
}
}
+ @Override
public boolean onInterceptTouchEvent(final MotionEvent ev) {
final int action = ev.getAction();
@@ -280,7 +285,8 @@
mCurrView.getLocationOnScreen(mTmpPos);
final int x = (int) ev.getRawX() - mTmpPos[0];
final int y = (int) ev.getRawY() - mTmpPos[1];
- mLongPressListener.onLongPress(mCurrView, x, y);
+ mLongPressListener.onLongPress(mCurrView, x, y,
+ NotificationMenuRow.getLongpressMenuItem(mContext));
}
}
};
@@ -379,6 +385,7 @@
animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+ @Override
public void onAnimationUpdate(ValueAnimator animation) {
onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
}
@@ -401,10 +408,12 @@
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
+ @Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
+ @Override
public void onAnimationEnd(Animator animation) {
updateSwipeProgressFromOffset(animView, canBeDismissed);
mDismissPendingMap.remove(animView);
@@ -435,6 +444,7 @@
public void snapChild(final View animView, final float targetLeft, float velocity) {
final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+ @Override
public void onAnimationUpdate(ValueAnimator animation) {
onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
}
@@ -447,6 +457,7 @@
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.addListener(new AnimatorListenerAdapter() {
+ @Override
public void onAnimationEnd(Animator animator) {
mSnappingChild = false;
updateSwipeProgressFromOffset(animView, canBeDismissed);
@@ -522,6 +533,7 @@
}
}
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLongPressSent) {
return true;
@@ -690,6 +702,6 @@
* Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
* @return whether the longpress was handled
*/
- boolean onLongPress(View v, int x, int y);
+ boolean onLongPress(View v, int x, int y, MenuItem item);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 926f610..f0de696 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -101,6 +101,10 @@
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeGutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -247,6 +251,7 @@
// which notification is currently being longpress-examined by the user
private NotificationGuts mNotificationGutsExposed;
+ private MenuItem mGutsMenuItem;
private KeyboardShortcuts mKeyboardShortcuts;
@@ -702,6 +707,7 @@
}
}
+ @Override
public void start() {
mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -967,7 +973,7 @@
entry.row.reInflateViews();
if (exposedGuts) {
mNotificationGutsExposed = entry.row.getGuts();
- bindGuts(entry.row);
+ bindGuts(entry.row, mGutsMenuItem);
}
inflateViews(entry, mStackScroller);
}
@@ -1032,6 +1038,7 @@
@Override
public boolean onDismiss() {
AsyncTask.execute(new Runnable() {
+ @Override
public void run() {
TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
@@ -1045,11 +1052,18 @@
}, false /* afterKeyguardGone */);
}
- private void bindGuts(final ExpandableNotificationRow row) {
+ protected void setNotificationSnoozed(StatusBarNotification sbn, long snoozeUntil) {
+ mNotificationListener.snoozeNotification(sbn.getKey(), snoozeUntil);
+ }
+
+ public SnoozeListener getSnoozeListener() {
+ return null;
+ }
+
+ private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
row.inflateGuts();
+ row.setGutsView(item);
final StatusBarNotification sbn = row.getStatusBarNotification();
- final NotificationChannel channel = row.getEntry().channel;
- PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier());
row.setTag(sbn.getPackageName());
final NotificationGuts guts = row.getGuts();
guts.setClosedListener((NotificationGuts g) -> {
@@ -1059,43 +1073,53 @@
mNotificationGutsExposed = null;
});
- final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ if (item.gutsContent instanceof SnoozeGutsContent) {
+ ((SnoozeGutsContent) item.gutsContent).setSnoozeListener(getSnoozeListener());
+ ((SnoozeGutsContent) item.gutsContent).setStatusBarNotification(sbn);
+ }
- final String pkg = sbn.getPackageName();
- final NotificationGuts.OnSettingsClickListener onSettingsClick =
- (View v, int appUid) -> {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
- guts.resetFalsingCheck();
- startAppNotificationSettingsActivity(pkg, appUid);
- };
- final View.OnClickListener onDoneClick =
- (View v) -> {
- // If the user has security enabled, show challenge if the setting is changed.
- if (guts.hasImportanceChanged()
- && isLockscreenPublicMode(sbn.getUser().getIdentifier())
- && (mState == StatusBarState.KEYGUARD
- || mState == StatusBarState.SHADE_LOCKED)) {
- OnDismissAction dismissAction = new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- closeControls(row, guts, v);
- return true;
- }
- };
- onLockedNotificationImportanceChange(dismissAction);
- } else {
- closeControls(row, guts, v);
- }
- };
- guts.bindNotification(pmUser, iNotificationManager, sbn, channel,
- onSettingsClick, onDoneClick, mNonBlockablePkgs);
+ if (item.gutsContent instanceof NotificationInfo) {
+ final NotificationChannel channel = row.getEntry().channel;
+ PackageManager pmUser = getPackageManagerForUser(mContext,
+ sbn.getUser().getIdentifier());
+ final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final String pkg = sbn.getPackageName();
+ NotificationInfo info = (NotificationInfo) item.gutsContent;
+ final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
+ int appUid) -> {
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ startAppNotificationSettingsActivity(pkg, appUid);
+ };
+ final View.OnClickListener onDoneClick = (View v) -> {
+ // If the user has security enabled, show challenge if the setting is changed.
+ if (info.hasImportanceChanged()
+ && isLockscreenPublicMode(sbn.getUser().getIdentifier())
+ && (mState == StatusBarState.KEYGUARD
+ || mState == StatusBarState.SHADE_LOCKED)) {
+ OnDismissAction dismissAction = new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ saveAndCloseNotificationMenu(info, row, guts, v);
+ return true;
+ }
+ };
+ onLockedNotificationImportanceChange(dismissAction);
+ } else {
+ saveAndCloseNotificationMenu(info, row, guts, v);
+ }
+ };
+ info.bindNotification(pmUser, iNotificationManager, sbn, channel, onSettingsClick,
+ onDoneClick,
+ mNonBlockablePkgs);
+ }
}
- private void closeControls(
+ private void saveAndCloseNotificationMenu(NotificationInfo info,
ExpandableNotificationRow row, NotificationGuts guts, View done) {
guts.resetFalsingCheck();
-
+ info.saveImportance();
int[] rowLocation = new int[2];
int[] doneLocation = new int[2];
row.getLocationOnScreen(rowLocation);
@@ -1111,7 +1135,8 @@
protected SwipeHelper.LongPressListener getNotificationLongClicker() {
return new SwipeHelper.LongPressListener() {
@Override
- public boolean onLongPress(View v, final int x, final int y) {
+ public boolean onLongPress(View v, final int x, final int y,
+ MenuItem item) {
if (!(v instanceof ExpandableNotificationRow)) {
return false;
}
@@ -1121,10 +1146,10 @@
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- bindGuts(row);
+ bindGuts(row, item);
+ NotificationGuts guts = row.getGuts();
// Assume we are a status_bar_notification_row
- final NotificationGuts guts = row.getGuts();
if (guts == null) {
// This view has no guts. Examples are the more card or the dismiss all view
return false;
@@ -1142,6 +1167,7 @@
guts.setVisibility(View.INVISIBLE);
// Post to ensure the the guts are properly laid out.
guts.post(new Runnable() {
+ @Override
public void run() {
if (row.getWindowToken() == null) {
Log.e(TAG, "Trying to show notification guts, but not attached to "
@@ -1172,6 +1198,7 @@
row.closeRemoteInput();
mStackScroller.onHeightChanged(row, true /* needsAnimation */);
mNotificationGutsExposed = guts;
+ mGutsMenuItem = item;
}
});
return true;
@@ -1483,6 +1510,7 @@
}
protected class H extends Handler {
+ @Override
public void handleMessage(Message m) {
switch (m.what) {
case MSG_SHOW_RECENT_APPS:
@@ -1754,6 +1782,7 @@
&& PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
mCurrentUserId);
dismissKeyguardThenExecute(new OnDismissAction() {
+ @Override
public boolean onDismiss() {
new Thread() {
@Override
@@ -1804,6 +1833,7 @@
private final class NotificationClicker implements View.OnClickListener {
private final int[] mTmpInt2 = new int[2];
+ @Override
public void onClick(final View v) {
if (!(v instanceof ExpandableNotificationRow)) {
Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
@@ -1847,6 +1877,7 @@
&& PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
mCurrentUserId);
dismissKeyguardThenExecute(new OnDismissAction() {
+ @Override
public boolean onDismiss() {
if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
// Release the HUN notification to the shade.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 013554c..f56d29d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -51,6 +51,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -120,7 +121,7 @@
private int mNotificationColor;
private ExpansionLogger mLogger;
private String mLoggingKey;
- private NotificationSettingsIconRow mSettingsIconRow;
+ private NotificationMenuRow mMenuRow;
private NotificationGuts mGuts;
private NotificationData.Entry mEntry;
private StatusBarNotification mStatusBarNotification;
@@ -132,7 +133,7 @@
private boolean mChildrenExpanded;
private boolean mIsSummaryWithChildren;
private NotificationChildrenContainer mChildrenContainer;
- private ViewStub mSettingsIconRowStub;
+ private ViewStub mMenuRowStub;
private ViewStub mGutsStub;
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
@@ -205,6 +206,7 @@
private Runnable mOnDismissRunnable;
private boolean mIsLowPriority;
+ @Override
public boolean isGroupExpansionChanging() {
if (isChildInGroup()) {
return mNotificationParent.isGroupExpansionChanging();
@@ -386,8 +388,8 @@
public void setAppName(String appName) {
mAppName = appName;
- if (mSettingsIconRow != null) {
- mSettingsIconRow.setAppName(mAppName);
+ if (mMenuRow != null) {
+ mMenuRow.setAppName(mAppName);
}
}
@@ -418,6 +420,7 @@
row.setIsChildInGroup(false, null);
}
+ @Override
public boolean isChildInGroup() {
return mNotificationParent != null;
}
@@ -455,7 +458,7 @@
@Override
protected boolean handleSlideBack() {
- if (mSettingsIconRow != null && mSettingsIconRow.isVisible()) {
+ if (mMenuRow != null && mMenuRow.isVisible()) {
animateTranslateNotification(0 /* targetLeft */);
return true;
}
@@ -679,6 +682,13 @@
mHeadsUpManager = headsUpManager;
}
+ public void setGutsView(MenuItem item) {
+ if (mGuts != null) {
+ item.gutsContent.setInteractionListener(mGuts);
+ mGuts.setGutsContent(item.gutsContent);
+ }
+ }
+
public void reInflateViews() {
initDimens();
if (mIsSummaryWithChildren) {
@@ -695,16 +705,16 @@
mGuts.setVisibility(oldGuts.getVisibility());
addView(mGuts, index);
}
- if (mSettingsIconRow != null) {
- View oldSettings = mSettingsIconRow;
- int settingsIndex = indexOfChild(oldSettings);
- removeView(oldSettings);
- mSettingsIconRow = (NotificationSettingsIconRow) LayoutInflater.from(mContext).inflate(
- R.layout.notification_settings_icon_row, this, false);
- mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
- mSettingsIconRow.setAppName(mAppName);
- mSettingsIconRow.setVisibility(oldSettings.getVisibility());
- addView(mSettingsIconRow, settingsIndex);
+ if (mMenuRow != null) {
+ View oldMenu = mMenuRow;
+ int menuIndex = indexOfChild(oldMenu);
+ removeView(oldMenu);
+ mMenuRow = (NotificationMenuRow) LayoutInflater.from(mContext).inflate(
+ R.layout.notification_menu_row, this, false);
+ mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
+ mMenuRow.setAppName(mAppName);
+ mMenuRow.setVisibility(oldMenu.getVisibility());
+ addView(mMenuRow, menuIndex);
}
for (NotificationContentView l : mLayouts) {
@@ -1033,21 +1043,19 @@
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
-
mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
for (NotificationContentView l : mLayouts) {
l.setExpandClickListener(mExpandClickListener);
l.setContainingNotification(this);
}
-
- mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub);
- mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+ mMenuRowStub = (ViewStub) findViewById(R.id.menu_row_stub);
+ mMenuRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
- mSettingsIconRow = (NotificationSettingsIconRow) inflated;
- mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
- mSettingsIconRow.setAppName(mAppName);
+ mMenuRow = (NotificationMenuRow) inflated;
+ mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
+ mMenuRow.setAppName(mAppName);
}
});
mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
@@ -1079,7 +1087,7 @@
mTranslateableViews.add(getChildAt(i));
}
// Remove views that don't translate
- mTranslateableViews.remove(mSettingsIconRowStub);
+ mTranslateableViews.remove(mMenuRowStub);
mTranslateableViews.remove(mChildrenContainerStub);
mTranslateableViews.remove(mGutsStub);
}
@@ -1094,8 +1102,8 @@
}
}
invalidateOutline();
- if (mSettingsIconRow != null) {
- mSettingsIconRow.resetState();
+ if (mMenuRow != null) {
+ mMenuRow.resetState(true /* notify */);
}
}
@@ -1122,8 +1130,8 @@
}
}
invalidateOutline();
- if (mSettingsIconRow != null) {
- mSettingsIconRow.updateSettingsIcons(translationX, getMeasuredWidth());
+ if (mMenuRow != null) {
+ mMenuRow.updateMenuAlpha(translationX, getMeasuredWidth());
}
}
@@ -1160,8 +1168,8 @@
@Override
public void onAnimationEnd(Animator anim) {
- if (!cancelled && mSettingsIconRow != null && leftTarget == 0) {
- mSettingsIconRow.resetState();
+ if (!cancelled && mMenuRow != null && leftTarget == 0) {
+ mMenuRow.resetState(true /* notify */);
mTranslateAnim = null;
}
}
@@ -1171,17 +1179,17 @@
}
public float getSpaceForGear() {
- if (mSettingsIconRow != null) {
- return mSettingsIconRow.getSpaceForGear();
+ if (mMenuRow != null) {
+ return mMenuRow.getSpaceForMenu();
}
return 0;
}
- public NotificationSettingsIconRow getSettingsRow() {
- if (mSettingsIconRow == null) {
- mSettingsIconRowStub.inflate();
+ public NotificationMenuRow getSettingsRow() {
+ if (mMenuRow == null) {
+ mMenuRowStub.inflate();
}
- return mSettingsIconRow;
+ return mMenuRow;
}
public void inflateGuts() {
@@ -1404,6 +1412,7 @@
}
}
+ @Override
public boolean isGroupExpanded() {
return mGroupManager.isGroupExpanded(mStatusBarNotification);
}
@@ -1457,8 +1466,8 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateMaxHeights();
- if (mSettingsIconRow != null) {
- mSettingsIconRow.updateVerticalLocation();
+ if (mMenuRow != null) {
+ mMenuRow.updateVerticalLocation();
}
updateContentShiftHeight();
}
@@ -1505,6 +1514,7 @@
mSensitiveHiddenInGeneral = hideSensitive;
}
+ @Override
public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
mHideSensitiveForIntrinsicHeight = hideSensitive;
if (mIsSummaryWithChildren) {
@@ -1517,6 +1527,7 @@
}
}
+ @Override
public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
long duration) {
boolean oldShowingPublic = mShowingPublic;
@@ -1581,6 +1592,7 @@
}
}
+ @Override
public boolean mustStayOnScreen() {
return mIsHeadsUp;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 4d0ce12..a1299e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -992,7 +992,6 @@
mStatusBarNotification = entry.notification;
mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
updateSingleLineView();
- applyRemoteInput(entry);
if (mContractedChild != null) {
mContractedWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
@@ -1005,6 +1004,7 @@
if (mAmbientChild != null) {
mAmbientWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
+ applyRemoteInput(entry);
updateShowingLegacyBackground();
mForceSelectNextLayout = true;
setDark(mDark, false /* animate */, 0 /* delay */);
@@ -1046,7 +1046,8 @@
View bigContentView = mExpandedChild;
if (bigContentView != null) {
mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
- mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput);
+ mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput,
+ mExpandedWrapper);
} else {
mExpandedRemoteInput = null;
}
@@ -1060,7 +1061,7 @@
View headsUpContentView = mHeadsUpChild;
if (headsUpContentView != null) {
mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput,
- mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput);
+ mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput, mHeadsUpWrapper);
} else {
mHeadsUpRemoteInput = null;
}
@@ -1074,7 +1075,7 @@
private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
boolean hasRemoteInput, PendingIntent existingPendingIntent,
- RemoteInputView cachedView) {
+ RemoteInputView cachedView, NotificationViewWrapper wrapper) {
View actionContainerCandidate = view.findViewById(
com.android.internal.R.id.actions_container);
if (actionContainerCandidate instanceof FrameLayout) {
@@ -1113,6 +1114,8 @@
mContext.getColor(R.color.remote_input_text_enabled),
mContext.getColor(R.color.remote_input_hint)));
+ existing.setWrapper(wrapper);
+
if (existingPendingIntent != null || existing.isActive()) {
// The current action could be gone, or the pending intent no longer valid.
// If we find a matching action in the new notification, focus, otherwise close.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 83104e6..f6056dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -40,6 +40,7 @@
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.CompoundButton;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
@@ -53,6 +54,8 @@
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.Set;
@@ -60,7 +63,8 @@
/**
* The guts of a notification revealed when performing a long press.
*/
-public class NotificationGuts extends LinearLayout {
+public class NotificationGuts extends FrameLayout
+ implements NotificationMenuRowProvider.GutsInteractionListener {
private static final String TAG = "NotificationGuts";
private static final long CLOSE_GUTS_DELAY = 8000;
@@ -69,24 +73,14 @@
private int mClipBottomAmount;
private int mActualHeight;
private boolean mExposed;
- private INotificationManager mINotificationManager;
- private int mStartingUserImportance;
- private StatusBarNotification mStatusBarNotification;
- private NotificationChannel mNotificationChannel;
-
- private View mImportanceGroup;
- private View mChannelDisabled;
- private Switch mChannelEnabledSwitch;
- private RadioButton mMinImportanceButton;
- private RadioButton mLowImportanceButton;
- private RadioButton mDefaultImportanceButton;
- private RadioButton mHighImportanceButton;
private Handler mHandler;
private Runnable mFalsingCheck;
private boolean mNeedsFalsingProtection;
private OnGutsClosedListener mListener;
+ private GutsContent mGutsContent;
+
public interface OnGutsClosedListener {
public void onGutsClosed(NotificationGuts guts);
}
@@ -103,11 +97,22 @@
}
}
};
- final TypedArray ta =
- context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Theme, 0, 0);
+ final TypedArray ta = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Theme, 0, 0);
ta.recycle();
}
+ public NotificationGuts(Context context) {
+ this(context, null);
+
+ }
+
+ public void setGutsContent(GutsContent content) {
+ mGutsContent = content;
+ removeAllViews();
+ addView(mGutsContent.getContentView());
+ }
+
public void resetFalsingCheck() {
mHandler.removeCallbacks(mFalsingCheck);
if (mNeedsFalsingProtection && mExposed) {
@@ -165,213 +170,23 @@
void onClick(View v, int appUid);
}
- void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
- final StatusBarNotification sbn, final NotificationChannel channel,
- OnSettingsClickListener onSettingsClick,
- OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
- mINotificationManager = iNotificationManager;
- mNotificationChannel = channel;
- mStatusBarNotification = sbn;
- mStartingUserImportance = channel.getImportance();
-
- final String pkg = sbn.getPackageName();
- int appUid = -1;
- String appname = pkg;
- Drawable pkgicon = null;
- try {
- final ApplicationInfo info = pm.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
- appUid = info.uid;
- appname = String.valueOf(pm.getApplicationLabel(info));
- pkgicon = pm.getApplicationIcon(info);
- }
- } catch (PackageManager.NameNotFoundException e) {
- // app is gone, just show package name and generic icon
- pkgicon = pm.getDefaultActivityIcon();
- }
-
- // If this is the placeholder channel, don't use our channel-specific text.
- String appNameText;
- CharSequence channelNameText;
- if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- appNameText = appname;
- channelNameText = mContext.getString(R.string.notification_header_default_channel);
- } else {
- appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
- channelNameText = channel.getName();
- }
- ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
- ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
-
- // Settings button.
- final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
- if (appUid >= 0 && onSettingsClick != null) {
- final int appUidF = appUid;
- settingsButton.setOnClickListener(
- (View view) -> { onSettingsClick.onClick(view, appUidF); });
- settingsButton.setText(R.string.notification_more_settings);
- } else {
- settingsButton.setVisibility(View.GONE);
- }
-
- // Done button.
- final TextView doneButton = (TextView) findViewById(R.id.done);
- doneButton.setText(R.string.notification_done);
- doneButton.setOnClickListener(onDoneClick);
-
- boolean nonBlockable = false;
- try {
- final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
- nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
- } catch (PackageManager.NameNotFoundException e) {
- // unlikely.
- }
- if (nonBlockablePkgs != null) {
- nonBlockable |= nonBlockablePkgs.contains(pkg);
- }
-
- final View importanceButtons = findViewById(R.id.importance_buttons);
- bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
-
- // Importance Text (hardcoded to 4 importance levels)
- final ViewGroup importanceTextGroup =
- (ViewGroup) findViewById(R.id.importance_buttons_text);
- final int size = importanceTextGroup.getChildCount();
- for (int i = 0; i < size; i++) {
- int importanceNameResId = 0;
- int importanceDescResId = 0;
- switch (i) {
- case 0:
- importanceNameResId = R.string.high_importance;
- importanceDescResId = R.string.notification_importance_high;
- break;
- case 1:
- importanceNameResId = R.string.default_importance;
- importanceDescResId = R.string.notification_importance_default;
- break;
- case 2:
- importanceNameResId = R.string.low_importance;
- importanceDescResId = R.string.notification_importance_low;
- break;
- case 3:
- importanceNameResId = R.string.min_importance;
- importanceDescResId = R.string.notification_importance_min;
- break;
- default:
- Log.e(TAG, "Too many importance groups in this layout.");
- break;
- }
- final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
- ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
- ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
- }
-
- // Top-level importance group
- mImportanceGroup = findViewById(R.id.importance);
- mChannelDisabled = findViewById(R.id.channel_disabled);
- updateImportanceGroup();
- }
-
- public boolean hasImportanceChanged() {
- return mStartingUserImportance != getSelectedImportance();
- }
-
- private void saveImportance() {
- int selectedImportance = getSelectedImportance();
- if (selectedImportance == mStartingUserImportance) {
- return;
- }
- MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
- selectedImportance - mStartingUserImportance);
- mNotificationChannel.setImportance(selectedImportance);
- try {
- mINotificationManager.updateNotificationChannelForPackage(
- mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
- mNotificationChannel);
- } catch (RemoteException e) {
- // :(
- }
- }
-
- private int getSelectedImportance() {
- if (!mChannelEnabledSwitch.isChecked()) {
- return NotificationManager.IMPORTANCE_NONE;
- } else if (mMinImportanceButton.isChecked()) {
- return NotificationManager.IMPORTANCE_MIN;
- } else if (mLowImportanceButton.isChecked()) {
- return NotificationManager.IMPORTANCE_LOW;
- } else if (mDefaultImportanceButton.isChecked()) {
- return NotificationManager.IMPORTANCE_DEFAULT;
- } else if (mHighImportanceButton.isChecked()) {
- return NotificationManager.IMPORTANCE_HIGH;
- } else {
- return NotificationManager.IMPORTANCE_UNSPECIFIED;
- }
- }
-
- private void bindToggles(final View importanceButtons, final int importance,
- final boolean nonBlockable) {
- // Enabled Switch
- mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
- mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
- mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
-
- // Importance Buttons
- mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
- mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
- mDefaultImportanceButton =
- (RadioButton) importanceButtons.findViewById(R.id.default_importance);
- mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
-
- // Set to current importance setting
- switch (importance) {
- case NotificationManager.IMPORTANCE_UNSPECIFIED:
- case NotificationManager.IMPORTANCE_NONE:
- break;
- case NotificationManager.IMPORTANCE_MIN:
- mMinImportanceButton.setChecked(true);
- break;
- case NotificationManager.IMPORTANCE_LOW:
- mLowImportanceButton.setChecked(true);
- break;
- case NotificationManager.IMPORTANCE_DEFAULT:
- mDefaultImportanceButton.setChecked(true);
- break;
- case NotificationManager.IMPORTANCE_HIGH:
- case NotificationManager.IMPORTANCE_MAX:
- mHighImportanceButton.setChecked(true);
- break;
- }
-
- // Callback when checked.
- mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
- resetFalsingCheck();
- updateImportanceGroup();
- });
- ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
- (buttonView, isChecked) -> { resetFalsingCheck(); });
- }
-
- private void updateImportanceGroup() {
- final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
- mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
- mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
- }
-
public void closeControls(int x, int y, boolean saveImportance) {
- if (saveImportance) {
- saveImportance();
- }
if (getWindowToken() == null) {
if (mListener != null) {
mListener.onGutsClosed(this);
}
return;
}
+ if (mGutsContent == null || !mGutsContent.handleCloseControls()) {
+ animateClose(x, y);
+ }
+ setExposed(false, mNeedsFalsingProtection);
+ if (mListener != null) {
+ mListener.onGutsClosed(this);
+ }
+ }
+
+ private void animateClose(int x, int y) {
if (x == -1 || y == -1) {
x = (getLeft() + getRight()) / 2;
y = (getTop() + getHeight() / 2);
@@ -391,10 +206,6 @@
}
});
a.start();
- setExposed(false, mNeedsFalsingProtection);
- if (mListener != null) {
- mListener.onGutsClosed(this);
- }
}
public void setActualHeight(int actualHeight) {
@@ -439,4 +250,14 @@
public boolean isExposed() {
return mExposed;
}
+
+ @Override
+ public void onInteraction(View view) {
+ resetFalsingCheck();
+ }
+
+ @Override
+ public void closeGuts(View view) {
+ closeControls(-1 /* x */, -1 /* y */, true /* notify */);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
new file mode 100644
index 0000000..0989846
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -0,0 +1,319 @@
+package com.android.systemui.statusbar;
+
+/*
+ * 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
+ */
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.SeekBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
+import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+import java.util.Set;
+
+/**
+ * The guts of a notification revealed when performing a long press.
+ */
+public class NotificationInfo extends LinearLayout implements GutsContent {
+ private static final String TAG = "InfoGuts";
+
+ private INotificationManager mINotificationManager;
+ private int mStartingUserImportance;
+ private StatusBarNotification mStatusBarNotification;
+ private NotificationChannel mNotificationChannel;
+
+ private ImageView mAutoButton;
+ private TextView mImportanceSummary;
+ private TextView mImportanceTitle;
+ private boolean mAuto;
+
+ private View mImportanceGroup;
+ private View mChannelDisabled;
+ private Switch mChannelEnabledSwitch;
+ private RadioButton mMinImportanceButton;
+ private RadioButton mLowImportanceButton;
+ private RadioButton mDefaultImportanceButton;
+ private RadioButton mHighImportanceButton;
+
+ private GutsInteractionListener mGutsInteractionListener;
+
+ public NotificationInfo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ interface OnSettingsClickListener {
+ void onClick(View v, int appUid);
+ }
+
+ void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
+ final StatusBarNotification sbn, final NotificationChannel channel,
+ OnSettingsClickListener onSettingsClick,
+ OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
+ mINotificationManager = iNotificationManager;
+ mNotificationChannel = channel;
+ mStatusBarNotification = sbn;
+ mStartingUserImportance = channel.getImportance();
+
+ final String pkg = sbn.getPackageName();
+ int appUid = -1;
+ String appname = pkg;
+ Drawable pkgicon = null;
+ try {
+ final ApplicationInfo info = pm.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ appUid = info.uid;
+ appname = String.valueOf(pm.getApplicationLabel(info));
+ pkgicon = pm.getApplicationIcon(info);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // app is gone, just show package name and generic icon
+ pkgicon = pm.getDefaultActivityIcon();
+ }
+
+ // If this is the placeholder channel, don't use our channel-specific text.
+ String appNameText;
+ CharSequence channelNameText;
+ if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ appNameText = appname;
+ channelNameText = mContext.getString(R.string.notification_header_default_channel);
+ } else {
+ appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
+ channelNameText = channel.getName();
+ }
+ ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
+ ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
+
+ // Settings button.
+ final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
+ if (appUid >= 0 && onSettingsClick != null) {
+ final int appUidF = appUid;
+ settingsButton.setOnClickListener(
+ (View view) -> {
+ onSettingsClick.onClick(view, appUidF);
+ });
+ settingsButton.setText(R.string.notification_more_settings);
+ } else {
+ settingsButton.setVisibility(View.GONE);
+ }
+
+ // Done button.
+ final TextView doneButton = (TextView) findViewById(R.id.done);
+ doneButton.setText(R.string.notification_done);
+ doneButton.setOnClickListener(onDoneClick);
+
+ boolean nonBlockable = false;
+ try {
+ final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
+ nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
+ } catch (PackageManager.NameNotFoundException e) {
+ // unlikely.
+ }
+ if (nonBlockablePkgs != null) {
+ nonBlockable |= nonBlockablePkgs.contains(pkg);
+ }
+
+ final View importanceButtons = findViewById(R.id.importance_buttons);
+ bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
+
+ // Importance Text (hardcoded to 4 importance levels)
+ final ViewGroup importanceTextGroup = (ViewGroup) findViewById(
+ R.id.importance_buttons_text);
+ final int size = importanceTextGroup.getChildCount();
+ for (int i = 0; i < size; i++) {
+ int importanceNameResId = 0;
+ int importanceDescResId = 0;
+ switch (i) {
+ case 0:
+ importanceNameResId = R.string.high_importance;
+ importanceDescResId = R.string.notification_importance_high;
+ break;
+ case 1:
+ importanceNameResId = R.string.default_importance;
+ importanceDescResId = R.string.notification_importance_default;
+ break;
+ case 2:
+ importanceNameResId = R.string.low_importance;
+ importanceDescResId = R.string.notification_importance_low;
+ break;
+ case 3:
+ importanceNameResId = R.string.min_importance;
+ importanceDescResId = R.string.notification_importance_min;
+ break;
+ default:
+ Log.e(TAG, "Too many importance groups in this layout.");
+ break;
+ }
+ final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
+ ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
+ ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
+ }
+
+ // Top-level importance group
+ mImportanceGroup = findViewById(R.id.importance);
+ mChannelDisabled = findViewById(R.id.channel_disabled);
+ updateImportanceGroup();
+ }
+
+ public boolean hasImportanceChanged() {
+ return mStartingUserImportance != getSelectedImportance();
+ }
+
+ public void saveImportance() {
+ int selectedImportance = getSelectedImportance();
+ if (selectedImportance == mStartingUserImportance) {
+ return;
+ }
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
+ selectedImportance - mStartingUserImportance);
+ mNotificationChannel.setImportance(selectedImportance);
+ try {
+ mINotificationManager.updateNotificationChannelForPackage(
+ mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
+ mNotificationChannel);
+ } catch (RemoteException e) {
+ // :(
+ }
+ }
+
+ private int getSelectedImportance() {
+ if (!mChannelEnabledSwitch.isChecked()) {
+ return NotificationManager.IMPORTANCE_NONE;
+ } else if (mMinImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_MIN;
+ } else if (mLowImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_LOW;
+ } else if (mDefaultImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_DEFAULT;
+ } else if (mHighImportanceButton.isChecked()) {
+ return NotificationManager.IMPORTANCE_HIGH;
+ } else {
+ return NotificationManager.IMPORTANCE_UNSPECIFIED;
+ }
+ }
+
+ private void bindToggles(final View importanceButtons, final int importance,
+ final boolean nonBlockable) {
+ // Enabled Switch
+ mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
+ mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
+ mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
+
+ // Importance Buttons
+ mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
+ mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
+ mDefaultImportanceButton = (RadioButton) importanceButtons
+ .findViewById(R.id.default_importance);
+ mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
+
+ // Set to current importance setting
+ switch (importance) {
+ case NotificationManager.IMPORTANCE_UNSPECIFIED:
+ case NotificationManager.IMPORTANCE_NONE:
+ break;
+ case NotificationManager.IMPORTANCE_MIN:
+ mMinImportanceButton.setChecked(true);
+ break;
+ case NotificationManager.IMPORTANCE_LOW:
+ mLowImportanceButton.setChecked(true);
+ break;
+ case NotificationManager.IMPORTANCE_DEFAULT:
+ mDefaultImportanceButton.setChecked(true);
+ break;
+ case NotificationManager.IMPORTANCE_HIGH:
+ case NotificationManager.IMPORTANCE_MAX:
+ mHighImportanceButton.setChecked(true);
+ break;
+ }
+
+ // Callback when checked.
+ mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ mGutsInteractionListener.onInteraction(NotificationInfo.this);
+ updateImportanceGroup();
+ });
+ ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ mGutsInteractionListener.onInteraction(NotificationInfo.this);
+ });
+ }
+
+ private void updateImportanceGroup() {
+ final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
+ mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
+ mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
+ }
+
+ public void closeControls() {
+ if (mGutsInteractionListener != null) {
+ mGutsInteractionListener.closeGuts(this);
+ }
+ }
+
+ @Override
+ public void setInteractionListener(GutsInteractionListener listener) {
+ mGutsInteractionListener = listener;
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public boolean handleCloseControls() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
new file mode 100644
index 0000000..fad63dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -0,0 +1,392 @@
+/*
+ * 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.statusbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.OnMenuClickListener;
+
+public class NotificationMenuRow extends FrameLayout
+ implements PluginListener<NotificationMenuRowProvider>, View.OnClickListener {
+
+ private static final int ICON_ALPHA_ANIM_DURATION = 200;
+
+ private ExpandableNotificationRow mParent;
+ private OnMenuClickListener mListener;
+ private NotificationMenuRowProvider mMenuProvider;
+ private ArrayList<MenuItem> mMenuItems = new ArrayList<>();
+
+ private ValueAnimator mFadeAnimator;
+ private boolean mMenuFadedIn = false;
+ private boolean mAnimating = false;
+ private boolean mOnLeft = true;
+ private boolean mDismissing = false;
+ private boolean mSnapping = false;
+ private boolean mIconsPlaced = false;
+
+ private int[] mIconLocation = new int[2];
+ private int[] mParentLocation = new int[2];
+
+ private float mHorizSpaceForIcon;
+ private int mVertSpaceForIcons;
+
+ private int mIconPadding;
+ private int mIconTint;
+
+ private float mAlpha = 0f;
+
+ public NotificationMenuRow(Context context) {
+ this(context, null);
+ }
+
+ public NotificationMenuRow(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs);
+ PluginManager.getInstance(getContext()).addPluginListener(
+ NotificationMenuRowProvider.ACTION, this,
+ NotificationMenuRowProvider.VERSION, false /* Allow multiple */);
+ mMenuItems.addAll(getDefaultNotificationMenuItems());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ final Resources res = getResources();
+ mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
+ mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
+ mIconTint = res.getColor(R.color.notification_gear_color);
+ updateMenu(false /* notify */);
+ }
+
+ public static MenuItem getLongpressMenuItem(Context context) {
+ Resources res = context.getResources();
+ Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
+ String settingsDescription = res.getString(R.string.notification_menu_gear_description);
+ NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate(
+ R.layout.notification_info, null, false);
+ MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
+ return settings;
+ }
+
+ public ArrayList<MenuItem> getDefaultNotificationMenuItems() {
+ ArrayList<MenuItem> items = new ArrayList<MenuItem>();
+ Resources res = getResources();
+
+ Drawable snoozeIcon = res.getDrawable(R.drawable.ic_snooze);
+ NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(mContext)
+ .inflate(R.layout.notification_snooze, null, false);
+ String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
+ MenuItem snooze = new MenuItem(snoozeIcon, snoozeDescription, content);
+ items.add(snooze);
+
+ Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
+ String settingsDescription = res.getString(R.string.notification_menu_gear_description);
+ NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(mContext).inflate(
+ R.layout.notification_info, null, false);
+ MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
+ items.add(settings);
+ return items;
+ }
+
+ private void updateMenu(boolean notify) {
+ removeAllViews();
+ mMenuItems.clear();
+ if (mMenuProvider != null) {
+ mMenuItems.addAll(mMenuProvider.getMenuItems(getContext()));
+ }
+ mMenuItems.addAll(getDefaultNotificationMenuItems());
+ for (int i = 0; i < mMenuItems.size(); i++) {
+ final View v = createMenuView(mMenuItems.get(i));
+ mMenuItems.get(i).menuView = v;
+ }
+ resetState(notify);
+ }
+
+ private View createMenuView(MenuItem item) {
+ AlphaOptimizedImageView iv = new AlphaOptimizedImageView(getContext());
+ addView(iv);
+ iv.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+ iv.setImageDrawable(item.icon);
+ iv.setOnClickListener(this);
+ iv.setColorFilter(mIconTint);
+ iv.setAlpha(mAlpha);
+ FrameLayout.LayoutParams lp = (LayoutParams) iv.getLayoutParams();
+ lp.width = (int) mHorizSpaceForIcon;
+ lp.height = (int) mHorizSpaceForIcon;
+ return iv;
+ }
+
+ public void resetState(boolean notify) {
+ setMenuAlpha(0f);
+ mIconsPlaced = false;
+ mMenuFadedIn = false;
+ mAnimating = false;
+ mSnapping = false;
+ mDismissing = false;
+ setMenuLocation(mOnLeft ? 1 : -1 /* on left */);
+ if (mListener != null && notify) {
+ mListener.onMenuReset(mParent);
+ }
+ }
+
+ public void setMenuClickListener(OnMenuClickListener listener) {
+ mListener = listener;
+ }
+
+ public void setNotificationRowParent(ExpandableNotificationRow parent) {
+ mParent = parent;
+ setMenuLocation(mOnLeft ? 1 : -1);
+ }
+
+ public void setAppName(String appName) {
+ Resources res = getResources();
+ final int count = mMenuItems.size();
+ for (int i = 0; i < count; i++) {
+ MenuItem item = mMenuItems.get(i);
+ String description = String.format(
+ res.getString(R.string.notification_menu_accessibility),
+ appName, item.menuDescription);
+ item.menuView.setContentDescription(description);
+ }
+ }
+
+ public ExpandableNotificationRow getNotificationParent() {
+ return mParent;
+ }
+
+ public void setMenuAlpha(float alpha) {
+ mAlpha = alpha;
+ if (alpha == 0) {
+ mMenuFadedIn = false; // Can fade in again once it's gone.
+ setVisibility(View.INVISIBLE);
+ } else {
+ setVisibility(View.VISIBLE);
+ }
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).setAlpha(mAlpha);
+ }
+ }
+
+ /**
+ * Returns whether the menu is displayed on the left side of the view or not.
+ */
+ public boolean isMenuOnLeft() {
+ return mOnLeft;
+ }
+
+ /**
+ * Returns the horizontal space in pixels required to display the menu.
+ */
+ public float getSpaceForMenu() {
+ return mHorizSpaceForIcon * getChildCount();
+ }
+
+ /**
+ * Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is
+ * visible.
+ */
+ public boolean isVisible() {
+ return mAlpha > 0;
+ }
+
+ public void cancelFadeAnimator() {
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ }
+
+ public void updateMenuAlpha(final float transX, final float size) {
+ if (mAnimating || !mMenuFadedIn) {
+ // Don't adjust when animating, or if the menu hasn't been shown yet.
+ return;
+ }
+
+ final float fadeThreshold = size * 0.3f;
+ final float absTrans = Math.abs(transX);
+ float desiredAlpha = 0;
+
+ if (absTrans == 0) {
+ desiredAlpha = 0;
+ } else if (absTrans <= fadeThreshold) {
+ desiredAlpha = 1;
+ } else {
+ desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
+ }
+ setMenuAlpha(desiredAlpha);
+ }
+
+ public void fadeInMenu(final boolean fromLeft, final float transX,
+ final float notiThreshold) {
+ if (mDismissing || mAnimating) {
+ return;
+ }
+ if (isMenuLocationChange(transX)) {
+ setMenuAlpha(0f);
+ }
+ setMenuLocation((int) transX);
+ mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1);
+ mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float absTrans = Math.abs(transX);
+
+ boolean pastGear = (fromLeft && transX <= notiThreshold)
+ || (!fromLeft && absTrans <= notiThreshold);
+ if (pastGear && !mMenuFadedIn) {
+ setMenuAlpha((float) animation.getAnimatedValue());
+ }
+ }
+ });
+ mFadeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mAnimating = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ // TODO should animate back to 0f from current alpha
+ setMenuAlpha(0f);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimating = false;
+ mMenuFadedIn = mAlpha == 1;
+ }
+ });
+ mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
+ mFadeAnimator.setDuration(ICON_ALPHA_ANIM_DURATION);
+ mFadeAnimator.start();
+ }
+
+ public void updateVerticalLocation() {
+ if (mParent == null || mMenuItems.size() == 0) {
+ return;
+ }
+ int parentHeight = mParent.getCollapsedHeight();
+ float translationY;
+ if (parentHeight < mVertSpaceForIcons) {
+ translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
+ } else {
+ translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
+ }
+ setTranslationY(translationY);
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ mIconsPlaced = false;
+ setMenuLocation(mOnLeft ? 1 : -1);
+ }
+
+ public void setMenuLocation(int translation) {
+ boolean onLeft = translation > 0;
+ if ((mIconsPlaced && onLeft == mOnLeft) || mSnapping || mParent == null) {
+ // Do nothing
+ return;
+ }
+ final boolean isRtl = mParent.isLayoutRtl();
+ final int count = getChildCount();
+ final int width = getWidth();
+ for (int i = 0; i < count; i++) {
+ final View v = getChildAt(i);
+ final float left = isRtl
+ ? -(width - mHorizSpaceForIcon * (i + 1))
+ : i * mHorizSpaceForIcon;
+ final float right = isRtl
+ ? -i * mHorizSpaceForIcon
+ : width - (mHorizSpaceForIcon * (i + 1));
+ v.setTranslationX(onLeft ? left : right);
+ }
+ mOnLeft = onLeft;
+ mIconsPlaced = true;
+ }
+
+ public boolean isMenuLocationChange(float translation) {
+ boolean onLeft = translation > mIconPadding;
+ boolean onRight = translation < -mIconPadding;
+ if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
+ return true;
+ }
+ return false;
+ }
+
+ public void setDismissing() {
+ mDismissing = true;
+ }
+
+ public void setSnapping(boolean snapping) {
+ mSnapping = snapping;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mListener == null) {
+ // Nothing to do
+ return;
+ }
+ v.getLocationOnScreen(mIconLocation);
+ mParent.getLocationOnScreen(mParentLocation);
+ final int centerX = (int) (mHorizSpaceForIcon / 2);
+ final int centerY = (int) (v.getTranslationY() * 2 + v.getHeight()) / 2;
+ final int x = mIconLocation[0] - mParentLocation[0] + centerX;
+ final int y = mIconLocation[1] - mParentLocation[1] + centerY;
+ final int index = indexOfChild(v);
+ mListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
+ }
+
+ @Override
+ public void onPluginConnected(NotificationMenuRowProvider plugin, Context pluginContext) {
+ mMenuProvider = plugin;
+ updateMenu(false /* notify */);
+ }
+
+ @Override
+ public void onPluginDisconnected(NotificationMenuRowProvider plugin) {
+ mMenuProvider = null;
+ updateMenu(false /* notify */);
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
deleted file mode 100644
index 4315647..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
+++ /dev/null
@@ -1,300 +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.statusbar;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-
-public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener {
-
- private static final int GEAR_ALPHA_ANIM_DURATION = 200;
-
- public interface SettingsIconRowListener {
- /**
- * Called when the gear behind a notification is touched.
- */
- public void onGearTouched(ExpandableNotificationRow row, int x, int y);
-
- /**
- * Called when a notification is slid back over the gear.
- */
- public void onSettingsIconRowReset(ExpandableNotificationRow row);
- }
-
- private ExpandableNotificationRow mParent;
- private AlphaOptimizedImageView mGearIcon;
- private float mHorizSpaceForGear;
- private SettingsIconRowListener mListener;
-
- private ValueAnimator mFadeAnimator;
- private boolean mSettingsFadedIn = false;
- private boolean mAnimating = false;
- private boolean mOnLeft = true;
- private boolean mDismissing = false;
- private boolean mSnapping = false;
- private boolean mIconPlaced = false;
-
- private int[] mGearLocation = new int[2];
- private int[] mParentLocation = new int[2];
- private int mVertSpaceForGear;
-
- public NotificationSettingsIconRow(Context context) {
- this(context, null);
- }
-
- public NotificationSettingsIconRow(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mGearIcon = (AlphaOptimizedImageView) findViewById(R.id.gear_icon);
- mGearIcon.setOnClickListener(this);
- setOnClickListener(this);
- mHorizSpaceForGear =
- getResources().getDimensionPixelOffset(R.dimen.notification_gear_width);
- mVertSpaceForGear = getResources().getDimensionPixelOffset(R.dimen.notification_min_height);
- resetState();
- }
-
- public void resetState() {
- setGearAlpha(0f);
- mIconPlaced = false;
- mSettingsFadedIn = false;
- mAnimating = false;
- mSnapping = false;
- mDismissing = false;
- setIconLocation(true /* on left */);
- if (mListener != null) {
- mListener.onSettingsIconRowReset(mParent);
- }
- }
-
- public void setGearListener(SettingsIconRowListener listener) {
- mListener = listener;
- }
-
- public void setNotificationRowParent(ExpandableNotificationRow parent) {
- mParent = parent;
- setIconLocation(mOnLeft);
- }
-
- public void setAppName(String appName) {
- Resources res = getResources();
- String description = String.format(res.getString(R.string.notification_gear_accessibility),
- appName);
- mGearIcon.setContentDescription(description);
- }
-
- public ExpandableNotificationRow getNotificationParent() {
- return mParent;
- }
-
- public void setGearAlpha(float alpha) {
- if (alpha == 0) {
- mSettingsFadedIn = false; // Can fade in again once it's gone.
- setVisibility(View.INVISIBLE);
- } else {
- setVisibility(View.VISIBLE);
- }
- mGearIcon.setAlpha(alpha);
- }
-
- /**
- * Returns whether the icon is on the left side of the view or not.
- */
- public boolean isIconOnLeft() {
- return mOnLeft;
- }
-
- /**
- * Returns the horizontal space in pixels required to display the gear behind a notification.
- */
- public float getSpaceForGear() {
- return mHorizSpaceForGear;
- }
-
- /**
- * Indicates whether the gear is visible at 1 alpha. Does not indicate
- * if entire view is visible.
- */
- public boolean isVisible() {
- return mGearIcon.getAlpha() > 0;
- }
-
- public void cancelFadeAnimator() {
- if (mFadeAnimator != null) {
- mFadeAnimator.cancel();
- }
- }
-
- public void updateSettingsIcons(final float transX, final float size) {
- if (mAnimating || !mSettingsFadedIn) {
- // Don't adjust when animating, or if the gear hasn't been shown yet.
- return;
- }
-
- final float fadeThreshold = size * 0.3f;
- final float absTrans = Math.abs(transX);
- float desiredAlpha = 0;
-
- if (absTrans == 0) {
- desiredAlpha = 0;
- } else if (absTrans <= fadeThreshold) {
- desiredAlpha = 1;
- } else {
- desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
- }
- setGearAlpha(desiredAlpha);
- }
-
- public void fadeInSettings(final boolean fromLeft, final float transX,
- final float notiThreshold) {
- if (mDismissing || mAnimating) {
- return;
- }
- if (isIconLocationChange(transX)) {
- setGearAlpha(0f);
- }
- setIconLocation(transX > 0 /* fromLeft */);
- mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
- mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- final float absTrans = Math.abs(transX);
-
- boolean pastGear = (fromLeft && transX <= notiThreshold)
- || (!fromLeft && absTrans <= notiThreshold);
- if (pastGear && !mSettingsFadedIn) {
- setGearAlpha((float) animation.getAnimatedValue());
- }
- }
- });
- mFadeAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mAnimating = true;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- // TODO should animate back to 0f from current alpha
- mGearIcon.setAlpha(0f);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimating = false;
- mSettingsFadedIn = mGearIcon.getAlpha() == 1;
- }
- });
- mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION);
- mFadeAnimator.start();
- }
-
- public void updateVerticalLocation() {
- if (mParent == null) {
- return;
- }
- int parentHeight = mParent.getCollapsedHeight();
- if (parentHeight < mVertSpaceForGear) {
- mGearIcon.setTranslationY((parentHeight / 2) - (mGearIcon.getHeight() / 2));
- } else {
- mGearIcon.setTranslationY((mVertSpaceForGear - mGearIcon.getHeight()) / 2);
- }
- }
-
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- setIconLocation(mOnLeft);
- }
-
- public void setIconLocation(boolean onLeft) {
- if ((mIconPlaced && onLeft == mOnLeft) || mSnapping || mParent == null
- || mGearIcon.getWidth() == 0) {
- // Do nothing
- return;
- }
- final boolean isRtl = mParent.isLayoutRtl();
- final float left = isRtl
- ? -(mParent.getWidth() - mHorizSpaceForGear)
- : 0;
- final float right = isRtl
- ? 0
- : mParent.getWidth() - mHorizSpaceForGear;
- final float centerX = ((mHorizSpaceForGear - mGearIcon.getWidth()) / 2);
- setTranslationX(onLeft ? left + centerX : right + centerX);
- mOnLeft = onLeft;
- mIconPlaced = true;
- }
-
- public boolean isIconLocationChange(float translation) {
- boolean onLeft = translation > mGearIcon.getPaddingStart();
- boolean onRight = translation < -mGearIcon.getPaddingStart();
- if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
- return true;
- }
- return false;
- }
-
- public void setDismissing() {
- mDismissing = true;
- }
-
- public void setSnapping(boolean snapping) {
- mSnapping = snapping;
- }
-
- @Override
- public void onClick(View v) {
- if (v.getId() == R.id.gear_icon) {
- if (mListener != null) {
- mGearIcon.getLocationOnScreen(mGearLocation);
- mParent.getLocationOnScreen(mParentLocation);
-
- final int centerX = (int) (mHorizSpaceForGear / 2);
- final int centerY =
- (int) (mGearIcon.getTranslationY() * 2 + mGearIcon.getHeight())/ 2;
- final int x = mGearLocation[0] - mParentLocation[0] + centerX;
- final int y = mGearLocation[1] - mParentLocation[1] + centerY;
- mListener.onGearTouched(mParent, x, y);
- }
- } else {
- // Do nothing when the background is touched.
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
new file mode 100644
index 0000000..670d73e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -0,0 +1,176 @@
+package com.android.systemui.statusbar;
+/*
+ * 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
+ */
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioGroup;
+import android.widget.RadioGroup.OnCheckedChangeListener;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.systemui.R;
+
+public class NotificationSnooze extends LinearLayout
+ implements NotificationMenuRowProvider.SnoozeGutsContent, View.OnClickListener {
+
+ private GutsInteractionListener mGutsInteractionListener;
+ private SnoozeListener mSnoozeListener;
+ private StatusBarNotification mSbn;
+
+ private TextView mSelectedOption;
+ private TextView mUndo;
+ private ViewGroup mSnoozeOptionView;
+
+ private long mTimeToSnooze;
+
+ // Default is the first option in this list
+ private static final int[] SNOOZE_OPTIONS = {
+ R.string.snooze_option_15_min, R.string.snooze_option_30_min,
+ R.string.snooze_option_1_hour
+ };
+
+ public NotificationSnooze(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ // Create the different options based on list
+ createOptionViews();
+
+ // Snackbar
+ mSelectedOption = (TextView) findViewById(R.id.snooze_option_default);
+ mSelectedOption.setOnClickListener(this);
+ mUndo = (TextView) findViewById(R.id.undo);
+ mUndo.setOnClickListener(this);
+
+ // Default to first option in list
+ setTimeToSnooze(SNOOZE_OPTIONS[0]);
+ }
+
+ private void createOptionViews() {
+ mSnoozeOptionView = (ViewGroup) findViewById(R.id.snooze_options);
+ mSnoozeOptionView.setVisibility(View.GONE);
+ final Resources res = getResources();
+ final int textSize = res.getDimensionPixelSize(R.dimen.snooze_option_text_size);
+ final int p = res.getDimensionPixelSize(R.dimen.snooze_option_padding);
+ for (int i = 0; i < SNOOZE_OPTIONS.length; i++) {
+ TextView tv = new TextView(getContext());
+ tv.setTextColor(Color.WHITE);
+ tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ tv.setPadding(p, p, p, p);
+ mSnoozeOptionView.addView(tv);
+ tv.setText(SNOOZE_OPTIONS[i]);
+ tv.setTag(SNOOZE_OPTIONS[i]);
+ tv.setOnClickListener(this);
+ }
+ }
+
+ private void showSnoozeOptions(boolean show) {
+ mSelectedOption.setVisibility(show ? View.GONE : View.VISIBLE);
+ mUndo.setVisibility(show ? View.GONE : View.VISIBLE);
+ mSnoozeOptionView.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
+ private void setTimeToSnooze(int optionId) {
+ long snoozeUntilMillis = Calendar.getInstance().getTimeInMillis();
+ switch (optionId) {
+ case R.string.snooze_option_15_min:
+ snoozeUntilMillis += TimeUnit.MINUTES.toMillis(15);
+ break;
+ case R.string.snooze_option_30_min:
+ snoozeUntilMillis += TimeUnit.MINUTES.toMillis(30);
+ break;
+ case R.string.snooze_option_1_hour:
+ snoozeUntilMillis += TimeUnit.MINUTES.toMillis(60);
+ break;
+ }
+ mTimeToSnooze = snoozeUntilMillis;
+ final Resources res = getResources();
+ String selectedString = String.format(
+ res.getString(R.string.snoozed_for_time), res.getString(optionId));
+ mSelectedOption.setText(selectedString);
+ showSnoozeOptions(false);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mGutsInteractionListener != null) {
+ mGutsInteractionListener.onInteraction(this);
+ }
+ final int id = v.getId();
+ final Integer tag = (Integer) v.getTag();
+ if (tag != null) {
+ // From the option list
+ setTimeToSnooze(tag);
+ } else if (id == R.id.snooze_option_default) {
+ // Show more snooze options
+ showSnoozeOptions(true);
+ } else if (id == R.id.undo) {
+ mTimeToSnooze = -1;
+ mGutsInteractionListener.closeGuts(this);
+ }
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public void setStatusBarNotification(StatusBarNotification sbn) {
+ mSbn = sbn;
+ }
+
+ @Override
+ public void setInteractionListener(GutsInteractionListener listener) {
+ mGutsInteractionListener = listener;
+ }
+
+ @Override
+ public void setSnoozeListener(SnoozeListener listener) {
+ mSnoozeListener = listener;
+ }
+
+ @Override
+ public boolean handleCloseControls() {
+ // When snooze is closed (i.e. there was interaction outside of the notification)
+ // then we commit the snooze action.
+ if (mSnoozeListener != null && mTimeToSnooze != -1) {
+ mSnoozeListener.snoozeNotification(mSbn, mTimeToSnooze);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index cd6c31f..1c89e32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -23,6 +23,7 @@
import android.util.ArraySet;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Interpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -298,5 +299,14 @@
TransformState otherState) {
return false;
}
+
+ /**
+ * Get a custom interpolator for this animation
+ * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY
+ * @param isFrom true if this transformation from the other view
+ */
+ public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) {
+ return null;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
new file mode 100644
index 0000000..de4c312
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
@@ -0,0 +1,79 @@
+/*
+ * 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.statusbar.notification;
+
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
+
+import static com.android.systemui.statusbar.TransformableView.TRANSFORMING_VIEW_TITLE;
+import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
+
+/**
+ * A custom transformation that modifies the interpolator
+ */
+public abstract class CustomInterpolatorTransformation
+ extends ViewTransformationHelper.CustomTransformation {
+
+ private final int mViewType;
+
+ public CustomInterpolatorTransformation(int viewType) {
+ mViewType = viewType;
+ }
+
+ @Override
+ public boolean transformTo(TransformState ownState, TransformableView notification,
+ float transformationAmount) {
+ if (!hasCustomTransformation()) {
+ return false;
+ }
+ TransformState otherState = notification.getCurrentState(mViewType);
+ if (otherState == null) {
+ return false;
+ }
+ View view = ownState.getTransformedView();
+ CrossFadeHelper.fadeOut(view, transformationAmount);
+ ownState.transformViewFullyTo(otherState, this, transformationAmount);
+ otherState.recycle();
+ return true;
+ }
+
+ protected boolean hasCustomTransformation() {
+ return true;
+ }
+
+ @Override
+ public boolean transformFrom(TransformState ownState,
+ TransformableView notification, float transformationAmount) {
+ if (!hasCustomTransformation()) {
+ return false;
+ }
+ TransformState otherState = notification.getCurrentState(mViewType);
+ if (otherState == null) {
+ return false;
+ }
+ View view = ownState.getTransformedView();
+ CrossFadeHelper.fadeIn(view, transformationAmount);
+ ownState.transformViewFullyFrom(otherState, this, transformationAmount);
+ otherState.recycle();
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index d564741..3b18886 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -40,9 +40,6 @@
private final ViewInvertHelper mInvertHelper;
private final Paint mGreyPaint = new Paint();
- private int mBackgroundColor = 0;
- private static final int CUSTOM_BACKGROUND_TAG = R.id.custom_background_color;
- private boolean mShouldInvertDark;
private boolean mShowingLegacyBackground;
protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) {
@@ -106,32 +103,6 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
- super.notifyContentUpdated(notification, isLowPriority);
- Drawable background = mView.getBackground();
- mBackgroundColor = 0;
- if (background instanceof ColorDrawable) {
- mBackgroundColor = ((ColorDrawable) background).getColor();
- mView.setBackground(null);
- mView.setTag(CUSTOM_BACKGROUND_TAG, mBackgroundColor);
- } else if (mView.getTag(CUSTOM_BACKGROUND_TAG) != null) {
- mBackgroundColor = (int) mView.getTag(CUSTOM_BACKGROUND_TAG);
- }
- mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor);
- }
-
- private boolean isColorLight(int backgroundColor) {
- return Color.alpha(backgroundColor) == 0
- || ColorUtils.calculateLuminance(backgroundColor) > 0.5;
- }
-
- @Override
- public int getCustomBackgroundColor() {
- // Parent notifications should always use the normal background color
- return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor;
- }
-
- @Override
public void setShowingLegacyBackground(boolean showing) {
super.setShowingLegacyBackground(showing);
mShowingLegacyBackground = showing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index e1f553c..8eab2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -31,9 +31,12 @@
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -43,17 +46,21 @@
import java.util.Stack;
+import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
+
/**
* Wraps a notification header view.
*/
public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
+ private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
+ = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
0, PorterDuff.Mode.SRC_ATOP);
private final int mIconDarkAlpha;
private final int mIconDarkColor = 0xffffffff;
- protected final ViewInvertHelper mInvertHelper;
+ protected final ViewInvertHelper mInvertHelper;
protected final ViewTransformationHelper mTransformationHelper;
protected int mColor;
@@ -70,6 +77,32 @@
mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
mTransformationHelper = new ViewTransformationHelper();
+
+ // we want to avoid that the header clashes with the other text when transforming
+ // low-priority
+ mTransformationHelper.setCustomTransformation(
+ new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) {
+
+ @Override
+ public Interpolator getCustomInterpolator(int interpolationType,
+ boolean isFrom) {
+ boolean isLowPriority = mView instanceof NotificationHeaderView;
+ if (interpolationType == TRANSFORM_Y) {
+ if (isLowPriority && !isFrom
+ || !isLowPriority && isFrom) {
+ return Interpolators.LINEAR_OUT_SLOW_IN;
+ } else {
+ return LOW_PRIORITY_HEADER_CLOSE;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean hasCustomTransformation() {
+ return mIsLowPriority;
+ }
+ }, TRANSFORMING_VIEW_TITLE);
resolveHeaderViews();
updateInvertHelper();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index 61457df..defeab2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -22,7 +22,11 @@
import android.content.Context;
import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
import android.view.View;
+import android.widget.TextView;
+
+import java.util.ArrayList;
/**
* Wraps a notification containing a messaging template
@@ -30,6 +34,7 @@
public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
private View mContractedMessage;
+ private ArrayList<View> mHistoricMessages = new ArrayList<View>();
protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
@@ -44,11 +49,18 @@
&& ((MessagingLinearLayout) container).getChildCount() > 0) {
MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
- // Only consider the first visible child - transforming to a position other than the
- // first looks bad because we have to move across other messages that are fading in.
int childCount = messagingContainer.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = messagingContainer.getChildAt(i);
+
+ if (child.getVisibility() == View.GONE
+ && child instanceof TextView
+ && !TextUtils.isEmpty(((TextView) child).getText())) {
+ mHistoricMessages.add(child);
+ }
+
+ // Only consider the first visible child - transforming to a position other than the
+ // first looks bad because we have to move across other messages that are fading in.
if (child.getId() == messagingContainer.getContractedChildId()) {
mContractedMessage = child;
} else if (child.getVisibility() == View.VISIBLE) {
@@ -75,4 +87,11 @@
mContractedMessage);
}
}
+
+ @Override
+ public void setRemoteInputVisible(boolean visible) {
+ for (int i = 0; i < mHistoricMessages.size(); i++) {
+ mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 8106223..836482a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -19,12 +19,17 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.ColorMatrix;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
+import android.support.v4.graphics.ColorUtils;
import android.view.NotificationHeaderView;
import android.view.View;
import com.android.systemui.Interpolators;
+import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -40,6 +45,8 @@
protected final View mView;
protected final ExpandableNotificationRow mRow;
protected boolean mDark;
+ private int mBackgroundColor = 0;
+ protected boolean mShouldInvertDark;
protected boolean mDarkInitialized = false;
public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
@@ -85,7 +92,19 @@
*/
public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
mDarkInitialized = false;
- };
+ Drawable background = mView.getBackground();
+ mBackgroundColor = 0;
+ if (background instanceof ColorDrawable) {
+ mBackgroundColor = ((ColorDrawable) background).getColor();
+ mView.setBackground(null);
+ }
+ mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor);
+ }
+
+ private boolean isColorLight(int backgroundColor) {
+ return Color.alpha(backgroundColor) == 0
+ || ColorUtils.calculateLuminance(backgroundColor) > 0.5;
+ }
protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
@@ -156,7 +175,8 @@
}
public int getCustomBackgroundColor() {
- return 0;
+ // Parent notifications should always use the normal background color
+ return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor;
}
public void setShowingLegacyBackground(boolean showing) {
@@ -164,4 +184,7 @@
public void setContentHeight(int contentHeight, int minHeightHint) {
}
+
+ public void setRemoteInputVisible(boolean visible) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 6b3d25d..d15ab10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -18,10 +18,10 @@
import android.util.ArraySet;
import android.util.Pools;
-import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -38,10 +38,11 @@
*/
public class TransformState {
+ public static final int TRANSFORM_X = 0x1;
+ public static final int TRANSFORM_Y = 0x10;
+ public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
+
private static final float UNDEFINED = -1f;
- private static final int TRANSOFORM_X = 0x1;
- private static final int TRANSOFORM_Y = 0x10;
- private static final int TRANSOFORM_ALL = TRANSOFORM_X | TRANSOFORM_Y;
private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
@@ -80,25 +81,31 @@
}
public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
- transformViewFrom(otherState, TRANSOFORM_ALL, null, transformationAmount);
+ transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
+ }
+
+ public void transformViewFullyFrom(TransformState otherState,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
+ transformViewFrom(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
}
public void transformViewVerticalFrom(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
- transformViewFrom(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
+ transformViewFrom(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
}
public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) {
- transformViewFrom(otherState, TRANSOFORM_Y, null, transformationAmount);
+ transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
}
private void transformViewFrom(TransformState otherState, int transformationFlags,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
final View transformedView = mTransformedView;
- boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
- boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
+ boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
+ boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
boolean transformScale = transformScale();
// lets animate the positions correctly
if (transformationAmount == 0.0f
@@ -153,14 +160,30 @@
float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
transformationAmount);
if (transformX) {
+ float interpolation = interpolatedValue;
+ if (customTransformation != null) {
+ Interpolator customInterpolator =
+ customTransformation.getCustomInterpolator(TRANSFORM_X, true /* isFrom */);
+ if (customInterpolator != null) {
+ interpolation = customInterpolator.getInterpolation(transformationAmount);
+ }
+ }
transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
0.0f,
- interpolatedValue));
+ interpolation));
}
if (transformY) {
+ float interpolation = interpolatedValue;
+ if (customTransformation != null) {
+ Interpolator customInterpolator =
+ customTransformation.getCustomInterpolator(TRANSFORM_Y, true /* isFrom */);
+ if (customInterpolator != null) {
+ interpolation = customInterpolator.getInterpolation(transformationAmount);
+ }
+ }
transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
0.0f,
- interpolatedValue));
+ interpolation));
}
if (transformScale) {
float transformationStartScaleX = getTransformationStartScaleX();
@@ -207,17 +230,23 @@
}
public void transformViewFullyTo(TransformState otherState, float transformationAmount) {
- transformViewTo(otherState, TRANSOFORM_ALL, null, transformationAmount);
+ transformViewTo(otherState, TRANSFORM_ALL, null, transformationAmount);
+ }
+
+ public void transformViewFullyTo(TransformState otherState,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
+ transformViewTo(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
}
public void transformViewVerticalTo(TransformState otherState,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
- transformViewTo(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
+ transformViewTo(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
}
public void transformViewVerticalTo(TransformState otherState, float transformationAmount) {
- transformViewTo(otherState, TRANSOFORM_Y, null, transformationAmount);
+ transformViewTo(otherState, TRANSFORM_Y, null, transformationAmount);
}
private void transformViewTo(TransformState otherState, int transformationFlags,
@@ -226,8 +255,8 @@
// lets animate the positions correctly
final View transformedView = mTransformedView;
- boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
- boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
+ boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
+ boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
boolean transformScale = transformScale();
// lets animate the positions correctly
if (transformationAmount == 0.0f) {
@@ -264,23 +293,37 @@
int[] ownPosition = getLaidOutLocationOnScreen();
if (transformX) {
float endX = otherStablePosition[0] - ownPosition[0];
- if (customTransformation != null
- && customTransformation.customTransformTarget(this, otherState)) {
- endX = mTransformationEndX;
+ float interpolation = interpolatedValue;
+ if (customTransformation != null) {
+ if (customTransformation.customTransformTarget(this, otherState)) {
+ endX = mTransformationEndX;
+ }
+ Interpolator customInterpolator =
+ customTransformation.getCustomInterpolator(TRANSFORM_X, false /* isFrom */);
+ if (customInterpolator != null) {
+ interpolation = customInterpolator.getInterpolation(transformationAmount);
+ }
}
transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
endX,
- interpolatedValue));
+ interpolation));
}
if (transformY) {
float endY = otherStablePosition[1] - ownPosition[1];
- if (customTransformation != null
- && customTransformation.customTransformTarget(this, otherState)) {
- endY = mTransformationEndY;
+ float interpolation = interpolatedValue;
+ if (customTransformation != null) {
+ if (customTransformation.customTransformTarget(this, otherState)) {
+ endY = mTransformationEndY;
+ }
+ Interpolator customInterpolator =
+ customTransformation.getCustomInterpolator(TRANSFORM_Y, false /* isFrom */);
+ if (customInterpolator != null) {
+ interpolation = customInterpolator.getInterpolation(transformationAmount);
+ }
}
transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
endY,
- interpolatedValue));
+ interpolation));
}
if (transformScale) {
View otherView = otherState.getTransformedView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 79120d8..7dcf811 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -18,6 +18,11 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK;
+import static com.android.systemui.tuner.LockscreenFragment.getIntentButton;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -31,7 +36,9 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -43,6 +50,7 @@
import android.provider.MediaStore;
import android.service.media.CameraPrewarmService;
import android.telecom.TelecomManager;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
@@ -74,6 +82,9 @@
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.PreviewInflater;
+import com.android.systemui.tuner.LockscreenFragment;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
@@ -81,7 +92,8 @@
*/
public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
UnlockMethodCache.OnUnlockMethodChangedListener,
- AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {
+ AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener,
+ Tunable {
final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
@@ -148,7 +160,13 @@
private Drawable mLeftAssistIcon;
private IntentButton mRightButton = new DefaultRightButton();
+ private IntentButton mRightDefault = mRightButton;
+ private IntentButton mRightPlugin;
+ private String mRightButtonStr;
private IntentButton mLeftButton = new DefaultLeftButton();
+ private IntentButton mLeftDefault = mLeftButton;
+ private IntentButton mLeftPlugin;
+ private String mLeftButtonStr;
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -245,6 +263,8 @@
mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */);
PluginManager.getInstance(getContext()).addPluginListener(LEFT_BUTTON_PLUGIN,
mLeftListener, IntentButtonProvider.VERSION, false /* Only allow one */);
+ TunerService.get(getContext()).addTunable(this, LockscreenFragment.LOCKSCREEN_LEFT_BUTTON,
+ LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON);
}
@Override
@@ -252,6 +272,7 @@
super.onDetachedFromWindow();
PluginManager.getInstance(getContext()).removePluginListener(mRightListener);
PluginManager.getInstance(getContext()).removePluginListener(mLeftListener);
+ TunerService.get(getContext()).removeTunable(this);
}
private void initAccessibility() {
@@ -552,8 +573,10 @@
if (mPhoneStatusBar.isKeyguardCurrentlySecure()) {
AsyncTask.execute(runnable);
} else {
+ boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
+ && TunerService.get(getContext()).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
mPhoneStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
- false /* dismissShade */, false /* afterKeyguardGone */, true /* deferred */);
+ dismissShade, false /* afterKeyguardGone */, true /* deferred */);
}
}
@@ -571,7 +594,9 @@
}
});
} else {
- mActivityStarter.startActivity(mLeftButton.getIntent(), false /* dismissShade */);
+ boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr)
+ && TunerService.get(getContext()).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
+ mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade);
}
}
@@ -763,15 +788,33 @@
inflateCameraPreview();
}
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (LockscreenFragment.LOCKSCREEN_LEFT_BUTTON.equals(key)) {
+ mLeftButtonStr = newValue;
+ mLeftIsVoiceAssist = TextUtils.isEmpty(mLeftButtonStr) && mLeftPlugin == null;
+ mLeftButton = getIntentButton(mContext, mLeftButtonStr, mLeftPlugin, mLeftDefault);
+ updateLeftAffordance();
+ } else {
+ mRightButtonStr = newValue;
+ mRightButton = getIntentButton(mContext, mRightButtonStr, mRightPlugin, mRightDefault);
+ updateRightAffordanceIcon();
+ updateCameraVisibility();
+ inflateCameraPreview();
+ }
+ }
+
private void setRightButton(IntentButton button) {
- mRightButton = button;
+ mRightPlugin = button;
+ mRightButton = getIntentButton(mContext, mRightButtonStr, mRightPlugin, mRightDefault);
updateRightAffordanceIcon();
updateCameraVisibility();
inflateCameraPreview();
}
private void setLeftButton(IntentButton button) {
- mLeftButton = button;
+ mLeftPlugin = button;
+ mLeftButton = getIntentButton(mContext, mLeftButtonStr, mLeftPlugin, mLeftDefault);
mLeftIsVoiceAssist = false;
updateLeftAffordance();
}
@@ -785,7 +828,7 @@
@Override
public void onPluginDisconnected(IntentButtonProvider plugin) {
- setRightButton(new DefaultRightButton());
+ setRightButton(null);
}
};
@@ -798,7 +841,7 @@
@Override
public void onPluginDisconnected(IntentButtonProvider plugin) {
- setLeftButton(new DefaultLeftButton());
+ setLeftButton(null);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index f04a9ee..9b4867e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -17,6 +17,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.Display;
@@ -47,6 +48,8 @@
private static final String TAG = "NavBarInflater";
public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
+ public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
+ public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
public static final String MENU_IME = "menu_ime";
public static final String BACK = "back";
@@ -55,6 +58,8 @@
public static final String NAVSPACE = "space";
public static final String CLIPBOARD = "clipboard";
public static final String KEY = "key";
+ public static final String LEFT = "left";
+ public static final String RIGHT = "right";
public static final String GRAVITY_SEPARATOR = ";";
public static final String BUTTON_SEPARATOR = ",";
@@ -130,7 +135,8 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+ TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
+ NAV_BAR_RIGHT);
PluginManager.getInstance(getContext()).addPluginListener(NavBarButtonProvider.ACTION, this,
NavBarButtonProvider.VERSION, true /* Allow multiple */);
}
@@ -148,6 +154,9 @@
clearViews();
inflateLayout(newValue);
}
+ } else if (NAV_BAR_LEFT.equals(key) || NAV_BAR_RIGHT.equals(key)) {
+ clearViews();
+ inflateLayout(mCurrentLayout);
}
}
@@ -268,6 +277,13 @@
boolean landscape) {
View v = null;
String button = extractButton(buttonSpec);
+ if (LEFT.equals(button)) {
+ buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_LEFT, NAVSPACE);
+ button = extractButton(buttonSpec);
+ } else if (RIGHT.equals(button)) {
+ buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_RIGHT, MENU_IME);
+ button = extractButton(buttonSpec);
+ }
// Let plugins go first so they can override a standard view if they want.
for (NavBarButtonProvider provider : mPlugins) {
v = provider.createView(buttonSpec, parent);
@@ -291,7 +307,14 @@
v = inflater.inflate(R.layout.custom_key, parent, false);
((KeyButtonView) v).setCode(code);
if (uri != null) {
- ((KeyButtonView) v).loadAsync(uri);
+ if (uri.contains(":")) {
+ ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
+ } else if (uri.contains("/")) {
+ int index = uri.indexOf('/');
+ String pkg = uri.substring(0, index);
+ int id = Integer.parseInt(uri.substring(index + 1));
+ ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
+ }
}
}
return v;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 8dcc693..7d82477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -141,6 +141,7 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
@@ -205,7 +206,7 @@
public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
- OnHeadsUpChangedListener, VisualStabilityManager.Callback {
+ OnHeadsUpChangedListener, VisualStabilityManager.Callback, SnoozeListener {
static final String TAG = "PhoneStatusBar";
public static final boolean DEBUG = BaseStatusBar.DEBUG;
public static final boolean SPEW = false;
@@ -4858,4 +4859,14 @@
}
}
+
+ @Override
+ public SnoozeListener getSnoozeListener() {
+ return this;
+ }
+
+ @Override
+ public void snoozeNotification(StatusBarNotification sbn, long snoozeUntil) {
+ setNotificationSnoozed(sbn, snoozeUntil);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 45cfbdc..882902e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -116,18 +116,18 @@
mOnClickListener = onClickListener;
}
- public void loadAsync(String uri) {
- new AsyncTask<String, Void, Drawable>() {
+ public void loadAsync(Icon icon) {
+ new AsyncTask<Icon, Void, Drawable>() {
@Override
- protected Drawable doInBackground(String... params) {
- return Icon.createWithContentUri(params[0]).loadDrawable(mContext);
+ protected Drawable doInBackground(Icon... params) {
+ return params[0].loadDrawable(mContext);
}
@Override
protected void onPostExecute(Drawable drawable) {
setImageDrawable(drawable);
}
- }.execute(uri);
+ }.execute(icon);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 44ec283..784f25e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -56,6 +56,7 @@
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.stack.ScrollContainer;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -90,6 +91,7 @@
private int mRevealR;
private boolean mResetting;
+ private NotificationViewWrapper mWrapper;
public RemoteInputView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -210,11 +212,17 @@
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(INVISIBLE);
+ if (mWrapper != null) {
+ mWrapper.setRemoteInputVisible(false);
+ }
}
});
reveal.start();
} else {
setVisibility(INVISIBLE);
+ if (mWrapper != null) {
+ mWrapper.setRemoteInputVisible(false);
+ }
}
}
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_CLOSE,
@@ -267,6 +275,9 @@
mEntry.notification.getPackageName());
setVisibility(VISIBLE);
+ if (mWrapper != null) {
+ mWrapper.setRemoteInputVisible(true);
+ }
mController.addRemoteInput(mEntry, mToken);
mEditText.setInnerFocusable(true);
mEditText.mShowImeOnInputConnection = true;
@@ -283,6 +294,10 @@
// Update came in after we sent the reply, time to reset.
reset();
}
+
+ if (isActive() && mWrapper != null) {
+ mWrapper.setRemoteInputVisible(true);
+ }
}
private void reset() {
@@ -452,6 +467,10 @@
super.dispatchFinishTemporaryDetach();
}
+ public void setWrapper(NotificationViewWrapper wrapper) {
+ mWrapper = wrapper;
+ }
+
/**
* An EditText that changes appearance based on whether it's focusable and becomes
* un-focusable whenever the user navigates away from it or it becomes invisible.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 395e8f2..b5ec398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -63,14 +63,15 @@
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationGuts;
-import com.android.systemui.statusbar.NotificationSettingsIconRow;
-import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
+import com.android.systemui.statusbar.NotificationMenuRow;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
@@ -94,7 +95,8 @@
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
- SettingsIconRowListener, ScrollContainer, VisibilityLocationProvider {
+ NotificationMenuRowProvider.OnMenuClickListener, ScrollContainer,
+ VisibilityLocationProvider {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -224,7 +226,7 @@
private int mMaxScrollAfterExpand;
private SwipeHelper.LongPressListener mLongPressListener;
- private NotificationSettingsIconRow mCurrIconRow;
+ private NotificationMenuRow mCurrIconRow;
private View mTranslatingParentView;
private View mGearExposedView;
@@ -399,16 +401,20 @@
}
@Override
- public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
- if (mLongPressListener != null) {
+ public void onMenuClicked(View view, int x, int y, MenuItem item) {
+ if (mLongPressListener == null) {
+ return;
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
row.getStatusBarNotification().getPackageName());
- mLongPressListener.onLongPress(row, x, y);
}
+ mLongPressListener.onLongPress(view, x, y, item);
}
@Override
- public void onSettingsIconRowReset(ExpandableNotificationRow row) {
+ public void onMenuReset(View row) {
if (mTranslatingParentView != null && row == mTranslatingParentView) {
mSwipeHelper.setSnappedToGear(false);
mGearExposedView = null;
@@ -425,7 +431,7 @@
if (DEBUG) {
int y = mTopPadding;
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
- y = (int) getLayoutHeight();
+ y = getLayoutHeight();
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
y = getHeight() - getEmptyBottomMargin();
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
@@ -911,7 +917,7 @@
mDragAnimPendingChildren.remove(animView);
}
if (mCurrIconRow != null && targetLeft == 0) {
- mCurrIconRow.resetState();
+ mCurrIconRow.resetState(true /* notify */);
mCurrIconRow = null;
}
}
@@ -4121,7 +4127,7 @@
if (currView instanceof ExpandableNotificationRow) {
// Set the listener for the current row's gear
mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
- mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
+ mCurrIconRow.setMenuClickListener(NotificationStackScrollLayout.this);
}
}
@@ -4133,9 +4139,9 @@
mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
// If the gear is visible and the movement is towards it it's not a location change.
- boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
+ boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isMenuOnLeft();
boolean locationChange = isTowardsGear(translation, onLeft)
- ? false : mCurrIconRow.isIconLocationChange(translation);
+ ? false : mCurrIconRow.isMenuLocationChange(translation);
if (locationChange) {
// Don't consider it "snapped" if location has changed.
setSnappedToGear(false);
@@ -4146,8 +4152,8 @@
mCheckForDrag = null;
} else {
// Check scheduled, reset alpha and update location; check will fade it in
- mCurrIconRow.setGearAlpha(0f);
- mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
+ mCurrIconRow.setMenuAlpha(0f);
+ mCurrIconRow.setMenuLocation((int) translation);
}
}
}
@@ -4198,14 +4204,14 @@
return false; // Let SwipeHelper handle it.
}
- boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
+ boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isMenuOnLeft());
boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
final double timeForGesture = ev.getEventTime() - ev.getDownTime();
final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView)
&& timeForGesture >= SWIPE_GEAR_TIMING;
if (mGearSnappedTo && mCurrIconRow.isVisible()) {
- if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
+ if (mGearSnappedOnLeft == mCurrIconRow.isMenuOnLeft()) {
boolean coveringGear =
Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
if (gestureTowardsGear || coveringGear) {
@@ -4249,7 +4255,7 @@
private void snapToGear(View animView, float velocity) {
final float snapBackThreshold = getSpaceForGear(animView);
- final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
+ final float target = mCurrIconRow.isMenuOnLeft() ? snapBackThreshold
: -snapBackThreshold;
mGearExposedView = mTranslatingParentView;
if (animView instanceof ExpandableNotificationRow) {
@@ -4280,7 +4286,7 @@
final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
final float translation = getTranslation(animView);
- return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
+ return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isMenuOnLeft()
? translation > snapBackThreshold
: translation < -snapBackThreshold);
}
@@ -4349,7 +4355,7 @@
* Indicates the the gear has been snapped to.
*/
private void setSnappedToGear(boolean snapped) {
- mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
+ mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isMenuOnLeft() : false;
mGearSnappedTo = snapped && mCurrIconRow != null;
}
@@ -4389,11 +4395,11 @@
final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
- || mCurrIconRow.isIconLocationChange(translation)))
+ || mCurrIconRow.isMenuLocationChange(translation)))
&& absTransX >= bounceBackToGearWidth * 0.4
&& absTransX < notiThreshold) {
// Fade in the gear
- mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
+ mCurrIconRow.fadeInMenu(translation > 0 /* fromLeft */, translation,
notiThreshold);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java
new file mode 100644
index 0000000..c9c780a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java
@@ -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.
+ */
+
+package com.android.systemui.tuner;
+
+import android.content.Context;
+import android.support.v7.preference.ListPreference;
+import android.util.AttributeSet;
+
+public class BetterListPreference extends ListPreference {
+
+ private CharSequence mSummary;
+
+ public BetterListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setSummary(CharSequence summary) {
+ super.setSummary(summary);
+ mSummary = summary;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mSummary;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
new file mode 100644
index 0000000..41786b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
@@ -0,0 +1,404 @@
+/*
+ * 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.tuner;
+
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Process;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
+import com.android.systemui.statusbar.phone.ExpandableIndicator;
+import com.android.systemui.tuner.ShortcutParser.Shortcut;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class LockscreenFragment extends PreferenceFragment {
+
+ private static final String KEY_LEFT = "left";
+ private static final String KEY_RIGHT = "right";
+ private static final String KEY_CUSTOMIZE = "customize";
+ private static final String KEY_SHORTCUT = "shortcut";
+
+ public static final String LOCKSCREEN_LEFT_BUTTON = "sysui_keyguard_left";
+ public static final String LOCKSCREEN_LEFT_UNLOCK = "sysui_keyguard_left_unlock";
+ public static final String LOCKSCREEN_RIGHT_BUTTON = "sysui_keyguard_right";
+ public static final String LOCKSCREEN_RIGHT_UNLOCK = "sysui_keyguard_right_unlock";
+
+ private final ArrayList<Tunable> mTunables = new ArrayList<>();
+ private TunerService mTunerService;
+ private Handler mHandler;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ mTunerService = TunerService.get(getContext());
+ mHandler = new Handler();
+ addPreferencesFromResource(R.xml.lockscreen_settings);
+ setupGroup((PreferenceGroup) findPreference(KEY_LEFT), LOCKSCREEN_LEFT_BUTTON,
+ LOCKSCREEN_LEFT_UNLOCK);
+ setupGroup((PreferenceGroup) findPreference(KEY_RIGHT), LOCKSCREEN_RIGHT_BUTTON,
+ LOCKSCREEN_RIGHT_UNLOCK);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mTunables.forEach(t -> mTunerService.removeTunable(t));
+ }
+
+ private void setupGroup(PreferenceGroup group, String buttonSetting, String unlockKey) {
+ SwitchPreference customize = (SwitchPreference) group.findPreference(KEY_CUSTOMIZE);
+ Preference shortcut = group.findPreference(KEY_SHORTCUT);
+ SwitchPreference unlock = (SwitchPreference) group.findPreference(unlockKey);
+ addTunable((k, v) -> {
+ boolean visible = v != null;
+ customize.setChecked(visible);
+ shortcut.setVisible(visible);
+ unlock.setVisible(visible);
+ if (visible) {
+ setSummary(shortcut, v);
+ }
+ }, buttonSetting);
+ customize.setOnPreferenceChangeListener((preference, newValue) -> {
+ boolean hasSetting = mTunerService.getValue(buttonSetting) != null;
+ if (hasSetting != (boolean) newValue) {
+ mHandler.post(() -> mTunerService.setValue(buttonSetting, hasSetting ? null : ""));
+ }
+ return true;
+ });
+ shortcut.setOnPreferenceClickListener(preference -> {
+ showSelectDialog(buttonSetting);
+ return true;
+ });
+ }
+
+ private void showSelectDialog(String buttonSetting) {
+ RecyclerView v = (RecyclerView) LayoutInflater.from(getContext())
+ .inflate(R.layout.tuner_shortcut_list, null);
+ v.setLayoutManager(new LinearLayoutManager(getContext()));
+ AlertDialog dialog = new Builder(getContext())
+ .setView(v)
+ .show();
+ Adapter adapter = new Adapter(getContext(), item -> {
+ mTunerService.setValue(buttonSetting, item.getSettingValue());
+ dialog.dismiss();
+ });
+ LauncherApps apps = getContext().getSystemService(LauncherApps.class);
+ List<LauncherActivityInfo> activities = apps.getActivityList(null,
+ Process.myUserHandle());
+
+ activities.forEach(info -> {
+ App app = new App(getContext(), info);
+ try {
+ new ShortcutParser(getContext(), info.getComponentName()).getShortcuts().forEach(
+ shortcut -> app.addChild(new StaticShortcut(getContext(), shortcut)));
+ } catch (NameNotFoundException e) {
+ }
+ adapter.addItem(app);
+ });
+
+ v.setAdapter(adapter);
+ }
+
+ private void setSummary(Preference shortcut, String value) {
+ if (value.contains("::")) {
+ Shortcut info = getShortcutInfo(getContext(), value);
+ shortcut.setSummary(info != null ? info.label : null);
+ } else if (value.contains("/")) {
+ ActivityInfo info = getActivityinfo(getContext(), value);
+ shortcut.setSummary(info != null ? info.loadLabel(getContext().getPackageManager())
+ : null);
+ } else {
+ shortcut.setSummary(null);
+ }
+ }
+
+ private void addTunable(Tunable t, String... keys) {
+ mTunables.add(t);
+ mTunerService.addTunable(t, keys);
+ }
+
+ public static ActivityInfo getActivityinfo(Context context, String value) {
+ ComponentName component = ComponentName.unflattenFromString(value);
+ try {
+ return context.getPackageManager().getActivityInfo(component, 0);
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ public static Shortcut getShortcutInfo(Context context, String value) {
+ return Shortcut.create(context, value);
+ }
+
+ public static class Holder extends ViewHolder {
+ public final ImageView icon;
+ public final TextView title;
+ public final ExpandableIndicator expand;
+
+ public Holder(View itemView) {
+ super(itemView);
+ icon = (ImageView) itemView.findViewById(android.R.id.icon);
+ title = (TextView) itemView.findViewById(android.R.id.title);
+ expand = (ExpandableIndicator) itemView.findViewById(R.id.expand);
+ }
+ }
+
+ private static class StaticShortcut extends Item {
+
+ private final Context mContext;
+ private final Shortcut mShortcut;
+
+
+ public StaticShortcut(Context context, Shortcut shortcut) {
+ mContext = context;
+ mShortcut = shortcut;
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return mShortcut.icon.loadDrawable(mContext);
+ }
+
+ @Override
+ public String getLabel() {
+ return mShortcut.label;
+ }
+
+ @Override
+ public String getSettingValue() {
+ return mShortcut.toString();
+ }
+
+ @Override
+ public Boolean getExpando() {
+ return null;
+ }
+ }
+
+ private static class App extends Item {
+
+ private final Context mContext;
+ private final LauncherActivityInfo mInfo;
+ private final ArrayList<Item> mChildren = new ArrayList<>();
+ private boolean mExpanded;
+
+ public App(Context context, LauncherActivityInfo info) {
+ mContext = context;
+ mInfo = info;
+ mExpanded = false;
+ }
+
+ public void addChild(Item child) {
+ mChildren.add(child);
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return mInfo.getBadgedIcon(mContext.getResources().getConfiguration().densityDpi);
+ }
+
+ @Override
+ public String getLabel() {
+ return mInfo.getLabel().toString();
+ }
+
+ @Override
+ public String getSettingValue() {
+ return mInfo.getComponentName().flattenToString();
+ }
+
+ @Override
+ public Boolean getExpando() {
+ return mChildren.size() != 0 ? mExpanded : null;
+ }
+
+ @Override
+ public void toggleExpando(Adapter adapter) {
+ mExpanded = !mExpanded;
+ if (mExpanded) {
+ mChildren.forEach(child -> adapter.addItem(this, child));
+ } else {
+ mChildren.forEach(child -> adapter.remItem(child));
+ }
+ }
+ }
+
+ private abstract static class Item {
+ public abstract Drawable getDrawable();
+
+ public abstract String getLabel();
+
+ public abstract String getSettingValue();
+
+ public abstract Boolean getExpando();
+
+ public void toggleExpando(Adapter adapter) {
+ }
+ }
+
+ public static class Adapter extends RecyclerView.Adapter<Holder> {
+ private ArrayList<Item> mItems = new ArrayList<>();
+ private final Context mContext;
+ private final Consumer<Item> mCallback;
+
+ public Adapter(Context context, Consumer<Item> callback) {
+ mContext = context;
+ mCallback = callback;
+ }
+
+ @Override
+ public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new Holder(LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.tuner_shortcut_item, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(Holder holder, int position) {
+ Item item = mItems.get(position);
+ holder.icon.setImageDrawable(item.getDrawable());
+ holder.title.setText(item.getLabel());
+ holder.itemView.setOnClickListener(
+ v -> mCallback.accept(mItems.get(holder.getAdapterPosition())));
+ Boolean expando = item.getExpando();
+ if (expando != null) {
+ holder.expand.setVisibility(View.VISIBLE);
+ holder.expand.setExpanded(expando);
+ holder.expand.setOnClickListener(
+ v -> mItems.get(holder.getAdapterPosition()).toggleExpando(Adapter.this));
+ } else {
+ holder.expand.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mItems.size();
+ }
+
+ public void addItem(Item item) {
+ mItems.add(item);
+ notifyDataSetChanged();
+ }
+
+ public void remItem(Item item) {
+ int index = mItems.indexOf(item);
+ mItems.remove(item);
+ notifyItemRemoved(index);
+ }
+
+ public void addItem(Item parent, Item child) {
+ int index = mItems.indexOf(parent);
+ mItems.add(index + 1, child);
+ notifyItemInserted(index + 1);
+ }
+ }
+
+ public static IntentButton getIntentButton(Context context, String buttonStr,
+ IntentButton plugin, IntentButton def) {
+ // Plugin wins.
+ if (plugin != null) return plugin;
+ // Then tuner options.
+ if (!TextUtils.isEmpty(buttonStr)) {
+ if (buttonStr.contains("::")) {
+ Shortcut shortcut = getShortcutInfo(context, buttonStr);
+ if (shortcut != null) {
+ return new ShortcutButton(context, shortcut);
+ }
+ } else if (buttonStr.contains("/")) {
+ ActivityInfo info = getActivityinfo(context, buttonStr);
+ if (info != null) {
+ return new ActivityButton(context, info);
+ }
+ }
+ }
+ // Then default.
+ return def;
+ }
+
+ private static class ShortcutButton implements IntentButton {
+ private final Shortcut mShortcut;
+ private final IconState mIconState;
+
+ public ShortcutButton(Context context, Shortcut shortcut) {
+ mShortcut = shortcut;
+ mIconState = new IconState();
+ mIconState.isVisible = true;
+ mIconState.drawable = shortcut.icon.loadDrawable(context);
+ mIconState.contentDescription = mShortcut.label;
+ }
+
+ @Override
+ public IconState getIcon() {
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return mShortcut.intent;
+ }
+ }
+
+ private static class ActivityButton implements IntentButton {
+ private final Intent mIntent;
+ private final IconState mIconState;
+
+ public ActivityButton(Context context, ActivityInfo info) {
+ mIntent = new Intent().setComponent(new ComponentName(info.packageName, info.name));
+ mIconState = new IconState();
+ mIconState.isVisible = true;
+ mIconState.drawable = info.loadIcon(context.getPackageManager());
+ mIconState.contentDescription = info.loadLabel(context.getPackageManager());
+ }
+
+ @Override
+ public IconState getIcon() {
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return mIntent;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index ad42459..9593c45 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.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,590 +14,227 @@
package com.android.systemui.tuner;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.helper.ItemTouchHelper;
-import android.util.TypedValue;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BACK;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BUTTON_SEPARATOR;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.CLIPBOARD;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.GRAVITY_SEPARATOR;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.HOME;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_LEFT;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_RIGHT;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.RECENT;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_END;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_START;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractButton;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractSize;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractImage;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractKeycode;
-public class NavBarTuner extends Fragment implements TunerService.Tunable {
+import android.annotation.Nullable;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.DropDownPreference;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.Preference.OnPreferenceClickListener;
+import android.support.v7.preference.PreferenceCategory;
+import android.text.SpannableStringBuilder;
+import android.text.style.ImageSpan;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.widget.EditText;
- private static final int SAVE = Menu.FIRST + 1;
- private static final int RESET = Menu.FIRST + 2;
- private static final int READ_REQUEST = 42;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
+import com.android.systemui.tuner.TunerService.Tunable;
- private static final float PREVIEW_SCALE = .95f;
- private static final float PREVIEW_SCALE_LANDSCAPE = .75f;
+import java.util.ArrayList;
- private NavBarAdapter mNavBarAdapter;
- private PreviewNavInflater mPreview;
+public class NavBarTuner extends PreferenceFragment {
+
+ private static final String LAYOUT = "layout";
+ private static final String LEFT = "left";
+ private static final String RIGHT = "right";
+
+ private static final String TYPE = "type";
+ private static final String KEYCODE = "keycode";
+ private static final String ICON = "icon";
+
+ private static final int[] ICONS = new int[]{
+ R.drawable.ic_qs_circle,
+ R.drawable.ic_add,
+ R.drawable.ic_remove,
+ R.drawable.ic_left,
+ R.drawable.ic_right,
+ R.drawable.ic_menu,
+ };
+
+ private final ArrayList<Tunable> mTunables = new ArrayList<>();
+ private Handler mHandler;
@Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.nav_bar_tuner, container, false);
- inflatePreview((ViewGroup) view.findViewById(R.id.nav_preview_frame));
- return view;
- }
-
- private void inflatePreview(ViewGroup view) {
- Display display = getActivity().getWindowManager().getDefaultDisplay();
- boolean isRotated = display.getRotation() == Surface.ROTATION_90
- || display.getRotation() == Surface.ROTATION_270;
-
- Configuration config = new Configuration(getContext().getResources().getConfiguration());
- boolean isPhoneLandscape = isRotated && (config.smallestScreenWidthDp < 600);
- final float scale = isPhoneLandscape ? PREVIEW_SCALE_LANDSCAPE : PREVIEW_SCALE;
- config.densityDpi = (int) (config.densityDpi * scale);
-
- mPreview = (PreviewNavInflater) LayoutInflater.from(getContext().createConfigurationContext(
- config)).inflate(R.layout.nav_bar_tuner_inflater, view, false);
- final ViewGroup.LayoutParams layoutParams = mPreview.getLayoutParams();
- layoutParams.width = (int) ((isPhoneLandscape ? display.getHeight() : display.getWidth())
- * scale);
- // Not sure why, but the height dimen is not being scaled with the dp, set it manually
- // for now.
- layoutParams.height = (int) (layoutParams.height * scale);
- if (isPhoneLandscape) {
- int width = layoutParams.width;
- layoutParams.width = layoutParams.height;
- layoutParams.height = width;
- }
- view.addView(mPreview);
-
- if (isRotated) {
- mPreview.findViewById(R.id.rot0).setVisibility(View.GONE);
- final View rot90 = mPreview.findViewById(R.id.rot90);
- } else {
- mPreview.findViewById(R.id.rot90).setVisibility(View.GONE);
- final View rot0 = mPreview.findViewById(R.id.rot0);
- }
- }
-
- private void notifyChanged() {
- mPreview.onTuningChanged(NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ mHandler = new Handler();
+ super.onCreate(savedInstanceState);
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- RecyclerView recyclerView = (RecyclerView) view.findViewById(android.R.id.list);
- final Context context = getContext();
- recyclerView.setLayoutManager(new LinearLayoutManager(context));
- mNavBarAdapter = new NavBarAdapter(context);
- recyclerView.setAdapter(mNavBarAdapter);
- recyclerView.addItemDecoration(new Dividers(context));
- final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mNavBarAdapter.mCallbacks);
- mNavBarAdapter.setTouchHelper(itemTouchHelper);
- itemTouchHelper.attachToRecyclerView(recyclerView);
-
- TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
- public void onDestroyView() {
- super.onDestroyView();
- TunerService.get(getContext()).removeTunable(this);
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ addPreferencesFromResource(R.xml.nav_bar_tuner);
+ bindLayout((ListPreference) findPreference(LAYOUT));
+ bindButton((PreferenceCategory) findPreference(LEFT),
+ NAV_BAR_LEFT, NAVSPACE);
+ bindButton((PreferenceCategory) findPreference(RIGHT),
+ NAV_BAR_RIGHT, MENU_IME);
}
@Override
- public void onTuningChanged(String key, String navLayout) {
- if (!NAV_BAR_VIEWS.equals(key)) return;
- Context context = getContext();
- if (navLayout == null) {
- navLayout = context.getString(R.string.config_navBarLayout);
- }
- String[] views = navLayout.split(GRAVITY_SEPARATOR);
- String[] groups = new String[] { NavBarAdapter.START, NavBarAdapter.CENTER,
- NavBarAdapter.END};
- CharSequence[] groupLabels = new String[] { getString(R.string.start),
- getString(R.string.center), getString(R.string.end) };
- mNavBarAdapter.clear();
- for (int i = 0; i < 3; i++) {
- mNavBarAdapter.addButton(groups[i], groupLabels[i]);
- for (String button : views[i].split(BUTTON_SEPARATOR)) {
- mNavBarAdapter.addButton(button, getLabel(button, context));
+ public void onDestroy() {
+ super.onDestroy();
+ mTunables.forEach(t -> TunerService.get(getContext()).removeTunable(t));
+ }
+
+ private void addTunable(Tunable tunable, String... keys) {
+ mTunables.add(tunable);
+ TunerService.get(getContext()).addTunable(tunable, keys);
+ }
+
+ private void bindLayout(ListPreference preference) {
+ addTunable((key, newValue) -> mHandler.post(() -> {
+ String val = newValue;
+ if (val == null) {
+ val = "default";
}
- }
- mNavBarAdapter.addButton(NavBarAdapter.ADD, getString(R.string.add_button));
- setHasOptionsMenu(true);
+ preference.setValue(val);
+ }), NAV_BAR_VIEWS);
+ preference.setOnPreferenceChangeListener((preference1, newValue) -> {
+ String val = (String) newValue;
+ if ("default".equals(val)) val = null;
+ TunerService.get(getContext()).setValue(NAV_BAR_VIEWS, val);
+ return true;
+ });
}
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- // TODO: Show save button conditionally, only when there are changes.
- menu.add(Menu.NONE, SAVE, Menu.NONE, getString(R.string.save))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(Menu.NONE, RESET, Menu.NONE, getString(R.string.reset));
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == SAVE) {
- if (!mNavBarAdapter.hasHomeButton()) {
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.no_home_title)
- .setMessage(R.string.no_home_message)
- .setPositiveButton(android.R.string.ok, null)
- .show();
+ private void bindButton(PreferenceCategory parent, String setting, String def) {
+ String k = parent.getKey();
+ DropDownPreference type = (DropDownPreference) findPreference(TYPE + "_" + k);
+ Preference keycode = findPreference(KEYCODE + "_" + k);
+ ListPreference icon = (ListPreference) findPreference(ICON + "_" + k);
+ setupIcons(icon);
+ addTunable((key, newValue) -> mHandler.post(() -> {
+ String val = newValue;
+ if (val == null) {
+ val = def;
+ }
+ String button = extractButton(val);
+ if (button.startsWith(KEY)) {
+ type.setValue(KEY);
+ String uri = extractImage(button);
+ int code = extractKeycode(button);
+ icon.setValue(uri);
+ updateSummary(icon);
+ keycode.setSummary(code + "");
+ keycode.setVisible(true);
+ icon.setVisible(true);
} else {
- Settings.Secure.putString(getContext().getContentResolver(),
- NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
+ type.setValue(button);
+ keycode.setVisible(false);
+ icon.setVisible(false);
}
- return true;
- } else if (item.getItemId() == RESET) {
- Settings.Secure.putString(getContext().getContentResolver(),
- NAV_BAR_VIEWS, null);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private static CharSequence getLabel(String button, Context context) {
- if (button.startsWith(HOME)) {
- return context.getString(R.string.accessibility_home);
- } else if (button.startsWith(BACK)) {
- return context.getString(R.string.accessibility_back);
- } else if (button.startsWith(RECENT)) {
- return context.getString(R.string.accessibility_recent);
- } else if (button.startsWith(NAVSPACE)) {
- return context.getString(R.string.space);
- } else if (button.startsWith(MENU_IME)) {
- return context.getString(R.string.menu_ime);
- } else if (button.startsWith(CLIPBOARD)) {
- return context.getString(R.string.clipboard);
- } else if (button.startsWith(KEY)) {
- return context.getString(R.string.keycode);
- }
- return button;
- }
-
- private static class Holder extends RecyclerView.ViewHolder {
- private TextView title;
-
- public Holder(View itemView) {
- super(itemView);
- title = (TextView) itemView.findViewById(android.R.id.title);
- }
- }
-
- private static class Dividers extends RecyclerView.ItemDecoration {
- private final Drawable mDivider;
-
- public Dividers(Context context) {
- TypedValue value = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.listDivider, value, true);
- mDivider = context.getDrawable(value.resourceId);
- }
-
- @Override
- public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- super.onDraw(c, parent, state);
- final int left = parent.getPaddingLeft();
- final int right = parent.getWidth() - parent.getPaddingRight();
-
- final int childCount = parent.getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = parent.getChildAt(i);
- final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
- .getLayoutParams();
- final int top = child.getBottom() + params.bottomMargin;
- final int bottom = top + mDivider.getIntrinsicHeight();
- mDivider.setBounds(left, top, right, bottom);
- mDivider.draw(c);
- }
- }
- }
-
- private void selectImage() {
- startActivityForResult(KeycodeSelectionHelper.getSelectImageIntent(), READ_REQUEST);
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == READ_REQUEST && resultCode == Activity.RESULT_OK && data != null) {
- final Uri uri = data.getData();
- final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION);
- getContext().getContentResolver().takePersistableUriPermission(uri, takeFlags);
- mNavBarAdapter.onImageSelected(uri);
- } else {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-
- private class NavBarAdapter extends RecyclerView.Adapter<Holder>
- implements View.OnClickListener {
-
- private static final String START = "start";
- private static final String CENTER = "center";
- private static final String END = "end";
- private static final String ADD = "add";
-
- private static final int ADD_ID = 0;
- private static final int BUTTON_ID = 1;
- private static final int CATEGORY_ID = 2;
-
- private List<String> mButtons = new ArrayList<>();
- private List<CharSequence> mLabels = new ArrayList<>();
- private int mCategoryLayout;
- private int mButtonLayout;
- private ItemTouchHelper mTouchHelper;
-
- // Stored keycode while we wait for image selection on a KEY.
- private int mKeycode;
-
- public NavBarAdapter(Context context) {
- TypedArray attrs = context.getTheme().obtainStyledAttributes(null,
- android.R.styleable.Preference, android.R.attr.preferenceStyle, 0);
- mButtonLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
- attrs = context.getTheme().obtainStyledAttributes(null,
- android.R.styleable.Preference, android.R.attr.preferenceCategoryStyle, 0);
- mCategoryLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
- }
-
- public void setTouchHelper(ItemTouchHelper itemTouchHelper) {
- mTouchHelper = itemTouchHelper;
- }
-
- public void clear() {
- mButtons.clear();
- mLabels.clear();
- notifyDataSetChanged();
- }
-
- public void addButton(String button, CharSequence label) {
- mButtons.add(button);
- mLabels.add(label);
- notifyItemInserted(mLabels.size() - 1);
- notifyChanged();
- }
-
- public boolean hasHomeButton() {
- final int N = mButtons.size();
- for (int i = 0; i < N; i++) {
- if (mButtons.get(i).startsWith(HOME)) {
- return true;
- }
- }
- return false;
- }
-
- public String getNavString() {
- StringBuilder builder = new StringBuilder();
- for (int i = 1; i < mButtons.size() - 1; i++) {
- String button = mButtons.get(i);
- if (button.equals(CENTER) || button.equals(END)) {
- if (builder.length() == 0 || builder.toString().endsWith(GRAVITY_SEPARATOR)) {
- // No start or center buttons, fill with a space.
- builder.append(NAVSPACE);
- }
- builder.append(GRAVITY_SEPARATOR);
- continue;
- } else if (builder.length() != 0 && !builder.toString().endsWith(
- GRAVITY_SEPARATOR)) {
- builder.append(BUTTON_SEPARATOR);
- }
- builder.append(button);
- }
- if (builder.toString().endsWith(GRAVITY_SEPARATOR)) {
- // No end buttons, fill with space.
- builder.append(NAVSPACE);
- }
- return builder.toString();
- }
-
- @Override
- public int getItemViewType(int position) {
- String button = mButtons.get(position);
- if (button.equals(START) || button.equals(CENTER) || button.equals(END)) {
- return CATEGORY_ID;
- }
- if (button.equals(ADD)) {
- return ADD_ID;
- }
- return BUTTON_ID;
- }
-
- @Override
- public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
- final Context context = parent.getContext();
- final LayoutInflater inflater = LayoutInflater.from(context);
- final View view = inflater.inflate(getLayoutId(viewType), parent, false);
- if (viewType == BUTTON_ID) {
- inflater.inflate(R.layout.nav_control_widget,
- (ViewGroup) view.findViewById(android.R.id.widget_frame));
- }
- return new Holder(view);
- }
-
- private int getLayoutId(int viewType) {
- if (viewType == CATEGORY_ID) {
- return mCategoryLayout;
- }
- return mButtonLayout;
- }
-
- @Override
- public void onBindViewHolder(Holder holder, int position) {
- holder.title.setText(mLabels.get(position));
- if (holder.getItemViewType() == BUTTON_ID) {
- bindButton(holder, position);
- } else if (holder.getItemViewType() == ADD_ID) {
- bindAdd(holder);
- }
- }
-
- private void bindAdd(Holder holder) {
- TypedValue value = new TypedValue();
- final Context context = holder.itemView.getContext();
- context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true);
- final ImageView icon = (ImageView) holder.itemView.findViewById(android.R.id.icon);
- icon.setImageResource(R.drawable.ic_add);
- icon.setImageTintList(ColorStateList.valueOf(context.getColor(value.resourceId)));
- holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
- holder.itemView.setClickable(true);
- holder.itemView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showAddDialog(v.getContext());
- }
+ }), setting);
+ OnPreferenceChangeListener listener = (preference, newValue) -> {
+ mHandler.post(() -> {
+ setValue(setting, type, keycode, icon);
+ updateSummary(icon);
});
- }
-
- private void bindButton(final Holder holder, int position) {
- holder.itemView.findViewById(android.R.id.icon_frame).setVisibility(View.GONE);
- holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
- bindClick(holder.itemView.findViewById(R.id.close), holder);
- bindClick(holder.itemView.findViewById(R.id.width), holder);
- holder.itemView.findViewById(R.id.drag).setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- mTouchHelper.startDrag(holder);
- return true;
- }
- });
- }
-
- private void showAddDialog(final Context context) {
- final String[] options = new String[] {
- BACK, HOME, RECENT, MENU_IME, NAVSPACE, CLIPBOARD, KEY,
- };
- final CharSequence[] labels = new CharSequence[options.length];
- for (int i = 0; i < options.length; i++) {
- labels[i] = getLabel(options[i], context);
- }
- new AlertDialog.Builder(context)
- .setTitle(R.string.select_button)
- .setItems(labels, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (KEY.equals(options[which])) {
- showKeyDialogs(context);
- } else {
- int index = mButtons.size() - 1;
- showAddedMessage(context, options[which]);
- mButtons.add(index, options[which]);
- mLabels.add(index, labels[which]);
-
- notifyItemInserted(index);
- notifyChanged();
- }
- }
- }).setNegativeButton(android.R.string.cancel, null)
- .show();
- }
-
- private void onImageSelected(Uri uri) {
- int index = mButtons.size() - 1;
- mButtons.add(index, KEY + KEY_CODE_START + mKeycode + KEY_IMAGE_DELIM + uri.toString()
- + KEY_CODE_END);
- mLabels.add(index, getLabel(KEY, getContext()));
-
- notifyItemInserted(index);
- notifyChanged();
- }
-
- private void showKeyDialogs(final Context context) {
- final KeycodeSelectionHelper.OnSelectionComplete listener =
- new KeycodeSelectionHelper.OnSelectionComplete() {
- @Override
- public void onSelectionComplete(int code) {
- mKeycode = code;
- selectImage();
- }
- };
- new AlertDialog.Builder(context)
- .setTitle(R.string.keycode)
- .setMessage(R.string.keycode_description)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- KeycodeSelectionHelper.showKeycodeSelect(context, listener);
- }
- }).show();
- }
-
- private void showAddedMessage(Context context, String button) {
- if (CLIPBOARD.equals(button)) {
- new AlertDialog.Builder(context)
- .setTitle(R.string.clipboard)
- .setMessage(R.string.clipboard_description)
- .setPositiveButton(android.R.string.ok, null)
- .show();
- }
- }
-
- private void bindClick(View view, Holder holder) {
- view.setOnClickListener(this);
- view.setTag(holder);
- }
-
- @Override
- public void onClick(View v) {
- Holder holder = (Holder) v.getTag();
- if (v.getId() == R.id.width) {
- showWidthDialog(holder, v.getContext());
- } else if (v.getId() == R.id.close) {
- int position = holder.getAdapterPosition();
- mButtons.remove(position);
- mLabels.remove(position);
- notifyItemRemoved(position);
- notifyChanged();
- }
- }
-
- private void showWidthDialog(final Holder holder, Context context) {
- final String buttonSpec = mButtons.get(holder.getAdapterPosition());
- float amount = extractSize(buttonSpec);
- final AlertDialog dialog = new AlertDialog.Builder(context)
- .setTitle(R.string.adjust_button_width)
- .setView(R.layout.nav_width_view)
- .setNegativeButton(android.R.string.cancel, null).create();
- dialog.setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(android.R.string.ok),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface d, int which) {
- final String button = extractButton(buttonSpec);
- SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
- if (seekBar.getProgress() == 75) {
- mButtons.set(holder.getAdapterPosition(), button);
- } else {
- float amount = (seekBar.getProgress() + 25) / 100f;
- mButtons.set(holder.getAdapterPosition(), button
- + SIZE_MOD_START + amount + SIZE_MOD_END);
- }
- notifyChanged();
- }
- });
- dialog.show();
- SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
- // Range is .25 - 1.75.
- seekBar.setMax(150);
- seekBar.setProgress((int) ((amount - .25f) * 100));
- }
-
- @Override
- public int getItemCount() {
- return mButtons.size();
- }
-
- private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
- @Override
- public boolean isLongPressDragEnabled() {
- return false;
- }
-
- @Override
- public boolean isItemViewSwipeEnabled() {
- return false;
- }
-
- @Override
- public int getMovementFlags(RecyclerView recyclerView,
- RecyclerView.ViewHolder viewHolder) {
- if (viewHolder.getItemViewType() != BUTTON_ID) {
- return makeMovementFlags(0, 0);
- }
- int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
- return makeMovementFlags(dragFlags, 0);
- }
-
- @Override
- public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
- RecyclerView.ViewHolder target) {
- int from = viewHolder.getAdapterPosition();
- int to = target.getAdapterPosition();
- if (to == 0) {
- // Can't go above the top.
- return false;
- }
- move(from, to, mButtons);
- move(from, to, mLabels);
- notifyChanged();
- notifyItemMoved(from, to);
- return true;
- }
-
- private <T> void move(int from, int to, List<T> list) {
- list.add(from > to ? to : to + 1, list.get(from));
- list.remove(from > to ? from + 1 : from);
- }
-
- @Override
- public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
- // Don't care.
- }
+ return true;
};
+ type.setOnPreferenceChangeListener(listener);
+ icon.setOnPreferenceChangeListener(listener);
+ keycode.setOnPreferenceClickListener(preference -> {
+ EditText editText = new EditText(getContext());
+ new AlertDialog.Builder(getContext())
+ .setTitle(preference.getTitle())
+ .setView(editText)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ int code = KeyEvent.KEYCODE_ENTER;
+ try {
+ code = Integer.parseInt(editText.getText().toString());
+ } catch (Exception e) {
+ }
+ keycode.setSummary(code + "");
+ setValue(setting, type, keycode, icon);
+ }).show();
+ return true;
+ });
+ }
+
+ private void updateSummary(ListPreference icon) {
+ try {
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14,
+ getContext().getResources().getDisplayMetrics());
+ String pkg = icon.getValue().split("/")[0];
+ int id = Integer.parseInt(icon.getValue().split("/")[1]);
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ Drawable d = Icon.createWithResource(pkg, id)
+ .loadDrawable(getContext());
+ d.setTint(Color.BLACK);
+ d.setBounds(0, 0, size, size);
+ ImageSpan span = new ImageSpan(d);
+ builder.append(" ", span, 0);
+ icon.setSummary(builder);
+ } catch (Exception e) {
+ Log.d("NavButton", "Problem with summary", e);
+ icon.setSummary(null);
+ }
+ }
+
+ private void setValue(String setting, DropDownPreference type, Preference keycode,
+ ListPreference icon) {
+ String button = type.getValue();
+ if (KEY.equals(button)) {
+ String uri = icon.getValue();
+ int code = KeyEvent.KEYCODE_ENTER;
+ try {
+ code = Integer.parseInt(keycode.getSummary().toString());
+ } catch (Exception e) {
+ }
+ button = button + KEY_CODE_START + code + KEY_IMAGE_DELIM + uri + KEY_CODE_END;
+ }
+ TunerService.get(getContext()).setValue(setting, button);
+ }
+
+ private void setupIcons(ListPreference icon) {
+ CharSequence[] labels = new CharSequence[ICONS.length];
+ CharSequence[] values = new CharSequence[ICONS.length];
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14,
+ getContext().getResources().getDisplayMetrics());
+ for (int i = 0; i < ICONS.length; i++) {
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ Drawable d = Icon.createWithResource(getContext().getPackageName(), ICONS[i])
+ .loadDrawable(getContext());
+ d.setTint(Color.BLACK);
+ d.setBounds(0, 0, size, size);
+ ImageSpan span = new ImageSpan(d);
+ builder.append(" ", span, 0);
+ labels[i] = builder;
+ values[i] = getContext().getPackageName() + "/" + ICONS[i];
+ }
+ icon.setEntries(labels);
+ icon.setEntryValues(values);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ShortcutParser.java b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutParser.java
new file mode 100644
index 0000000..2aa51b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutParser.java
@@ -0,0 +1,162 @@
+/*
+ * 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.tuner;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShortcutParser {
+ private static final String SHORTCUTS = "android.app.shortcuts";
+ private static final String SHORTCUT = "shortcut";
+ private static final String INTENT = "intent";
+
+ private final Context mContext;
+ private final String mPkg;
+ private final int mResId;
+ private final String mName;
+ private Resources mResources;
+ private AttributeSet mAttrs;
+
+ public ShortcutParser(Context context, ComponentName component) throws NameNotFoundException {
+ this(context, component.getPackageName(), component.getClassName(),
+ getResId(context, component));
+ }
+
+ private static int getResId(Context context, ComponentName component)
+ throws NameNotFoundException {
+ ActivityInfo i = context.getPackageManager().getActivityInfo(
+ component, PackageManager.GET_META_DATA);
+ int resId = 0;
+ if (i.metaData != null && i.metaData.containsKey(SHORTCUTS)) {
+ resId = i.metaData.getInt(SHORTCUTS);
+ }
+ return resId;
+ }
+
+ public ShortcutParser(Context context, String pkg, String name, int resId) {
+ mContext = context;
+ mPkg = pkg;
+ mResId = resId;
+ mName = name;
+ }
+
+ public List<Shortcut> getShortcuts() {
+ List<Shortcut> list = new ArrayList<>();
+ if (mResId != 0) {
+ try {
+ mResources = mContext.getPackageManager().getResourcesForApplication(mPkg);
+ XmlResourceParser parser = mResources.getXml(mResId);
+ mAttrs = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (parser.getName().equals(SHORTCUT)) {
+ Shortcut c = parseShortcut(parser);
+ if (c != null) {
+ list.add(c);
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ return list;
+ }
+
+ private Shortcut parseShortcut(XmlResourceParser parser)
+ throws IOException, XmlPullParserException {
+ final TypedArray sa = mResources.obtainAttributes(mAttrs, R.styleable.Shortcut);
+ Shortcut c = new Shortcut();
+
+ final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true);
+ if (!enabled) return null;
+ final String id = sa.getString(R.styleable.Shortcut_shortcutId);
+ final int iconResId = sa.getResourceId(R.styleable.Shortcut_icon, 0);
+ final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutShortLabel, 0);
+
+ c.pkg = mPkg;
+ c.icon = Icon.createWithResource(mPkg, iconResId);
+ c.id = id;
+ c.label = mResources.getString(titleResId);
+ c.name = mName;
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_TAG) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (parser.getName().equals(INTENT)) {
+ c.intent = Intent.parseIntent(mResources, parser, mAttrs);
+ }
+ }
+ return c.intent != null ? c : null;
+ }
+
+ public static class Shortcut {
+ public Intent intent;
+ public String label;
+ public Icon icon;
+ public String pkg;
+ public String id;
+ public String name;
+
+ public static Shortcut create(Context context, String value) {
+ String[] sp = value.split("::");
+ try {
+ for (Shortcut shortcut : new ShortcutParser(context,
+ new ComponentName(sp[0], sp[1])).getShortcuts()) {
+ if (shortcut.id.equals(sp[2])) {
+ return shortcut;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(pkg);
+ builder.append("::");
+ builder.append(name);
+ builder.append("::");
+ builder.append(id);
+ return builder.toString();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 565ac08..fb94061 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -33,6 +33,7 @@
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;
@@ -126,6 +127,12 @@
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);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
index cac0806..166fcb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
@@ -63,7 +63,7 @@
private static final String TEST_CHANNEL = "test_channel";
private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
- private NotificationGuts mNotificationGuts;
+ private NotificationInfo mNotificationInfo;
private final INotificationManager mMockINotificationManager = mock(INotificationManager.class);
private final PackageManager mMockPackageManager = mock(PackageManager.class);
private NotificationChannel mNotificationChannel;
@@ -76,7 +76,7 @@
// Inflate the layout
final LayoutInflater layoutInflater =
LayoutInflater.from(InstrumentationRegistry.getTargetContext());
- mNotificationGuts = (NotificationGuts) layoutInflater.inflate(R.layout.notification_guts,
+ mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
null);
// PackageManager must return a packageInfo and applicationInfo.
@@ -98,18 +98,18 @@
@UiThreadTest
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.pkgname);
+ final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
}
@Test
@UiThreadTest
public void testBindNotification_SetsTextChannelName() throws Exception {
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.channel_name);
+ final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -117,12 +117,12 @@
@UiThreadTest
public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel,
(View v, int appUid) -> { latch.countDown(); }, null, null);
- final TextView settingsButton =
- (TextView) mNotificationGuts.findViewById(R.id.more_settings);
+ final TextView settingsButton =
+ (TextView) mNotificationInfo.findViewById(R.id.more_settings);
settingsButton.performClick();
// Verify that listener was triggered.
assertEquals(0, latch.getCount());
@@ -132,12 +132,12 @@
@UiThreadTest
public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null,
(View v) -> { latch.countDown(); },
null);
- final TextView doneButton = (TextView) mNotificationGuts.findViewById(R.id.done);
+ final TextView doneButton = (TextView) mNotificationInfo.findViewById(R.id.done);
doneButton.performClick();
// Verify that listener was triggered.
assertEquals(0, latch.getCount());
@@ -146,38 +146,38 @@
@Test
@UiThreadTest
public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- assertFalse(mNotificationGuts.hasImportanceChanged());
+ assertFalse(mNotificationInfo.hasImportanceChanged());
}
@Test
@UiThreadTest
public void testHasImportanceChanged_ReturnsTrueAfterButtonChecked() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
// Find the high button and check it.
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
- assertTrue(mNotificationGuts.hasImportanceChanged());
+ assertTrue(mNotificationInfo.hasImportanceChanged());
}
@Test
@UiThreadTest
public void testImportanceButtonCheckedBasedOnInitialImportance() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
assertTrue(highButton.isChecked());
}
@Test
@UiThreadTest
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
@@ -187,10 +187,10 @@
@UiThreadTest
public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
@@ -199,10 +199,10 @@
@Test
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception {
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -211,10 +211,10 @@
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnspecified() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -223,12 +223,12 @@
@UiThreadTest
public void testCloseControls_CallsUpdateNotificationChannelIfChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
assertEquals(NotificationManager.IMPORTANCE_HIGH, mNotificationChannel.getImportance());
@@ -238,12 +238,12 @@
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfSaveFalse() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+ RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
- mNotificationGuts.closeControls(-1, -1, false);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -252,10 +252,10 @@
@UiThreadTest
public void testEnabledSwitchOnByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertTrue(enabledSwitch.isChecked());
}
@@ -263,10 +263,10 @@
@UiThreadTest
public void testEnabledSwitchVisibleByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
}
@@ -274,11 +274,11 @@
@UiThreadTest
public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null,
Collections.singleton(TEST_PACKAGE_NAME));
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
}
@@ -286,13 +286,13 @@
@UiThreadTest
public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null,
Collections.singleton(TEST_PACKAGE_NAME));
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
@@ -301,14 +301,14 @@
@UiThreadTest
public void testEnabledSwitchOverridesOtherButtons() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
- Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
- RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
+ RadioButton lowButton = (RadioButton) mNotificationInfo.findViewById(R.id.low_importance);
lowButton.setChecked(true);
enabledSwitch.setChecked(false);
- mNotificationGuts.closeControls(-1, -1, true);
+ mNotificationInfo.closeControls();
assertEquals(NotificationManager.IMPORTANCE_NONE, mNotificationChannel.getImportance());
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 6779657..47ac1ce 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -102,7 +102,6 @@
}
}
}
-
};
private HandlerCaller mHandlerCaller;
@@ -313,7 +312,6 @@
(new AutoFillManagerServiceShellCommand(this)).exec(
this, in, out, err, args, callback, resultReceiver);
}
-
}
private final class SettingsObserver extends ContentObserver {
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index 83faf1b..77e7b31 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -16,7 +16,11 @@
package com.android.server.autofill;
-import static com.android.server.autofill.AutoFillManagerService.DEBUG;
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.bundleToString;
+import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_ERROR;
+import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_REQUESTED;
+import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_SUCCESS;
import android.annotation.Nullable;
import android.app.Activity;
@@ -31,12 +35,16 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
-import android.icu.text.DateFormat;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintService;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.Binder;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.service.autofill.AutoFillService;
@@ -45,6 +53,7 @@
import android.service.autofill.IAutoFillServerCallback;
import android.service.autofill.IAutoFillService;
import android.service.voice.VoiceInteractionSession;
+import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -59,7 +68,6 @@
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -73,7 +81,7 @@
private static final String TAG = "AutoFillManagerServiceImpl";
/** Used do assign ids to new ServerCallback instances. */
- private static int sServerCallbackCounter = 0;
+ private static int sSessionIdCounter = 0;
private final int mUserId;
private final int mUid;
@@ -106,13 +114,15 @@
};
/**
- * Cache of pending ServerCallbacks, keyed by {@link ServerCallback#id}.
+ * Cache of pending {@link Session}s, keyed by {@link Session#mId}.
*
- * <p>They're kept until the AutoFillService handles a request, or an error occurs.
+ * <p>They're kept until the {@link AutoFillService} finished handling a request, an error
+ * occurs, or the session times out.
*/
- // TODO(b/33197203): need to make sure service is bound while callback is pending
+ // TODO(b/33197203): need to make sure service is bound while callback is pending and/or
+ // use WeakReference
@GuardedBy("mLock")
- private static final SparseArray<ServerCallback> mServerCallbacks = new SparseArray<>();
+ private static final SparseArray<Session> mSessions = new SparseArray<>();
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
@@ -146,10 +156,9 @@
}
};
-
/**
* Receiver of assist data from the app's {@link Activity}, uses the {@code resultData} as
- * the {@link ServerCallback#id}.
+ * the {@link Session#mId}.
*/
private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
@Override
@@ -165,22 +174,24 @@
.getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0);
- final ServerCallback serverCallback;
+ final Session session;
synchronized (mLock) {
- serverCallback = mServerCallbacks.get(resultCode);
- if (serverCallback == null) {
+ session = mSessions.get(resultCode);
+ if (session == null) {
Slog.w(TAG, "no server callback for id " + resultCode);
return;
}
- serverCallback.appCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+ session.mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
}
- mService.autoFill(structure, serverCallback, serverCallback.extras, flags);
+ mService.autoFill(structure, session.mServerCallback, session.mExtras, flags);
}
};
@GuardedBy("mLock")
private IAutoFillService mService;
+ @GuardedBy("mLock")
private boolean mBound;
+ @GuardedBy("mLock")
private boolean mValid;
// Estimated time when the service will be evicted from the cache.
@@ -274,8 +285,8 @@
activityToken = topActivities.get(0);
}
- final String historyItem =
- DateFormat.getDateTimeInstance().format(new Date()) + " - " + activityToken;
+ final String historyItem = TimeUtils.formatForLogging(System.currentTimeMillis())
+ + " - " + activityToken;
synchronized (mLock) {
mRequestHistory.add(historyItem);
requestAutoFillLocked(activityToken, extras, flags, true);
@@ -294,9 +305,9 @@
return;
}
- final int callbackId = ++sServerCallbackCounter;
- final ServerCallback serverCallback = new ServerCallback(callbackId, extras);
- mServerCallbacks.put(callbackId, serverCallback);
+ final int sessionId = ++sSessionIdCounter;
+ final Session session = new Session(sessionId, extras);
+ mSessions.put(sessionId, session);
/*
* TODO(b/33197203): apply security checks below:
@@ -307,7 +318,7 @@
*/
try {
// TODO(b/33197203): add MetricsLogger call
- if (!mAm.requestAutoFillData(mAssistReceiver, null, callbackId, activityToken, flags)) {
+ if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken, flags)) {
// TODO(b/33197203): might need a way to warn user (perhaps a new method on
// AutoFillService).
Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
@@ -347,39 +358,74 @@
/**
* Called by {@link AutoFillUI} to fill an activity after the user selected a dataset.
*/
- void autoFillApp(int callbackId, Dataset dataset) {
+ void autoFillApp(int sessionId, Dataset dataset) {
// TODO(b/33197203): add MetricsLogger call
if (dataset == null) {
- Slog.w(TAG, "autoFillApp(): no dataset for callback id " + callbackId);
+ Slog.w(TAG, "autoFillApp(): no dataset for callback id " + sessionId);
return;
}
- final ServerCallback serverCallback;
+
+ final Session session;
synchronized (mLock) {
- serverCallback = mServerCallbacks.get(callbackId);
- if (serverCallback == null) {
- Slog.w(TAG, "autoFillApp(): no server callback with id " + callbackId);
+ session = mSessions.get(sessionId);
+ if (session == null) {
+ Slog.w(TAG, "autoFillApp(): no session with id " + sessionId);
return;
}
- if (serverCallback.appCallback == null) {
- Slog.w(TAG, "autoFillApp(): no app callback for server callback " + callbackId);
+ if (session.mAppCallback == null) {
+ Slog.w(TAG, "autoFillApp(): no app callback for session " + sessionId);
return;
}
+
// TODO(b/33197203): use a handler?
- try {
- if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
- serverCallback.appCallback.autoFill(dataset);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error auto-filling activity: " + e);
- }
- removeServerCallbackLocked(callbackId);
+ session.autoFill(dataset);
}
}
- void removeServerCallbackLocked(int id) {
- if (DEBUG) Slog.d(TAG, "Removing " + id + " from server callbacks");
- mServerCallbacks.remove(id);
+ void removeSessionLocked(int id) {
+ if (DEBUG) Slog.d(TAG, "Removing session " + id);
+ mSessions.remove(id);
+
+ // TODO(b/33197203): notify mService so it can invalidate the FillCallback / SaveCallback?
+ }
+
+ /**
+ * Notifies the result of a {@link FillResponse} authentication request to the service.
+ *
+ * <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after user
+ * used the fingerprint sensors to authenticate.
+ */
+ void notifyResponseAuthenticationResult(Bundle extras, int flags) {
+ if (DEBUG) Slog.d(TAG, "notifyResponseAuthenticationResult(): flags=" + flags
+ + ", extras=" + bundleToString(extras));
+
+ synchronized (mLock) {
+ try {
+ mService.authenticateFillResponse(extras, flags);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error sending authentication result back to service: " + e);
+ }
+ }
+ }
+
+ /**
+ * Notifies the result of a {@link Dataset} authentication request to the service.
+ *
+ * <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after
+ * it gets the results from a fingerprint authentication.
+ */
+ void notifyDatasetAuthenticationResult(Bundle extras, int flags) {
+ if (DEBUG) Slog.d(TAG, "notifyDatasetAuthenticationResult(): flags=" + flags
+ + ", extras=" + bundleToString(extras));
+ synchronized (mLock) {
+ try {
+ mService.authenticateDataset(extras, flags);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error sending authentication result back to service: " + e);
+ }
+ }
}
void dumpLocked(String prefix, PrintWriter pw) {
@@ -398,15 +444,15 @@
pw.print(prefix); pw.print("mUserId="); pw.println(mUserId);
pw.print(prefix); pw.print("mUid="); pw.println(mUid);
pw.print(prefix); pw.print("mComponent="); pw.println(mComponent.flattenToShortString());
+ pw.print(prefix); pw.print("mService: "); pw.println(mService);
pw.print(prefix); pw.print("mBound="); pw.println(mBound);
- pw.print(prefix); pw.print("mService="); pw.println(mService);
pw.print(prefix); pw.print("mEstimateTimeOfDeath=");
TimeUtils.formatDuration(mEstimateTimeOfDeath, SystemClock.uptimeMillis(), pw);
pw.println();
if (DEBUG) {
// ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
- pw.print(prefix); pw.println("Service info:");
+ pw.print(prefix); pw.println("ServiceInfo:");
mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
}
@@ -427,19 +473,19 @@
}
}
- pw.print(prefix); pw.print("sServerCallbackCounter="); pw.println(sServerCallbackCounter);
- final int size = mServerCallbacks.size();
+ pw.print(prefix); pw.print("sSessionIdCounter="); pw.println(sSessionIdCounter);
+ final int size = mSessions.size();
if (size == 0) {
- pw.print(prefix); pw.println("No server callbacks");
+ pw.print(prefix); pw.println("No sessions");
} else {
- pw.print(prefix); pw.print(size); pw.println(" server callbacks:");
+ pw.print(prefix); pw.print(size); pw.println(" sessions:");
for (int i = 0; i < size; i++) {
- pw.print(prefix2); pw.print(mServerCallbacks.keyAt(i));
- final ServerCallback callback = mServerCallbacks.valueAt(i);
- if (callback.appCallback == null) {
+ pw.print(prefix2); pw.print(mSessions.keyAt(i));
+ final Session session = mSessions.valueAt(i);
+ if (session.mAppCallback == null) {
pw.println("(no appCallback)");
} else {
- pw.print(" (app callback: "); pw.print(callback.appCallback) ; pw.println(")");
+ pw.print(" (app callback: "); pw.print(session.mAppCallback) ; pw.println(")");
}
}
pw.println();
@@ -448,7 +494,7 @@
@Override
public String toString() {
- return "[AutoFillManagerServiceImpl: userId=" + mUserId + ", uid=" + mUid
+ return "AutoFillManagerServiceImpl: [userId=" + mUserId + ", uid=" + mUid
+ ", component=" + mComponent.flattenToShortString() + "]";
}
@@ -472,49 +518,311 @@
/**
* A bridge between the {@link AutoFillService} implementation and the activity being
* auto-filled (represented through the {@link IAutoFillAppCallback}).
+ *
+ * <p>Although the auto-fill requests and callbacks are stateless from the service's point of
+ * view, we need to keep state in the framework side for cases such as authentication. For
+ * example, when service return a {@link FillResponse} that contains all the fields needed
+ * to fill the activity but it requires authentication first, that response need to be held
+ * until the user authenticates or it times out.
*/
- private final class ServerCallback extends IAutoFillServerCallback.Stub {
+ // TODO(b/33197203): make sure sessions are removed (and tested by CTS):
+ // - On all authentication scenarios.
+ // - When user does not interact back after a while.
+ // - When service is unbound.
+ private final class Session {
- private final int id;
- private final Bundle extras;
- private IAutoFillAppCallback appCallback;
+ private final int mId;
+ private final Bundle mExtras;
+ private IAutoFillAppCallback mAppCallback;
- private ServerCallback(int id, Bundle extras) {
- this.id = id;
- this.extras = extras;
+ // Token used on fingerprint authentication
+ private final IBinder mToken = new Binder();
+
+ private final IFingerprintService mFingerprintService;
+
+ @GuardedBy("mLock")
+ private FillResponse mResponseRequiringAuth;
+ @GuardedBy("mLock")
+ private Dataset mDatasetRequiringAuth;
+
+ // Used to auto-fill the activity directly when the FillCallback.onResponse() is called as
+ // the result of a successful user authentication on service's side.
+ @GuardedBy("mLock")
+ private boolean mAutoFillDirectly;
+
+ // TODO(b/33197203): use handler to handle results?
+ // TODO(b/33197203): handle all callback methods and/or cancelation?
+ private IFingerprintServiceReceiver mServiceReceiver =
+ new IFingerprintServiceReceiver.Stub() {
+
+ @Override
+ public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
+ if (DEBUG) Slog.d(TAG, "onEnrollResult()");
+ }
+
+ @Override
+ public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+ if (DEBUG) Slog.d(TAG, "onAcquired()");
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
+ if (DEBUG) Slog.d(TAG, "onAuthenticationSucceeded(): " + fp.getGroupId());
+
+ // First, check what was authenticated, a response or a dataset.
+ // Then, decide how to handle it:
+ // - If service provided data, handle them directly.
+ // - Otherwise, notify service.
+
+ mAutoFillDirectly = true;
+
+ if (mDatasetRequiringAuth != null) {
+ if (mDatasetRequiringAuth.isEmpty()) {
+ notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(),
+ FLAG_AUTHENTICATION_SUCCESS);
+ } else {
+ autoFillAppLocked(mDatasetRequiringAuth, true);
+ }
+ } else if (mResponseRequiringAuth != null) {
+ final List<Dataset> datasets = mResponseRequiringAuth.getDatasets();
+ if (datasets.isEmpty()) {
+ notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(),
+ FLAG_AUTHENTICATION_SUCCESS);
+ } else {
+ showResponseLocked(mResponseRequiringAuth, true);
+ }
+ } else {
+ Slog.w(TAG, "onAuthenticationSucceeded(): no response or dataset");
+ }
+
+ mUi.dismissFingerprintRequest(mUserId, true);
+ }
+
+ @Override
+ public void onAuthenticationFailed(long deviceId) {
+ if (DEBUG) Slog.d(TAG, "onAuthenticationFailed()");
+ // Do nothing - onError() will be called after a few failures...
+ }
+
+ @Override
+ public void onError(long deviceId, int error, int vendorCode) {
+ if (DEBUG) Slog.d(TAG, "onError()");
+
+ // Notify service so it can fallback to its own authentication
+ if (mDatasetRequiringAuth != null) {
+ notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(),
+ FLAG_AUTHENTICATION_ERROR);
+ } else if (mResponseRequiringAuth != null) {
+ notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(),
+ FLAG_AUTHENTICATION_ERROR);
+ } else {
+ Slog.w(TAG, "onError(): no response or dataset");
+ }
+
+ mUi.dismissFingerprintRequest(mUserId, false);
+ }
+
+ @Override
+ public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+ if (DEBUG) Slog.d(TAG, "onRemoved()");
+ }
+
+ @Override
+ public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) {
+ if (DEBUG) Slog.d(TAG, "onEnumerated()");
+ }
+ };
+
+ private IAutoFillServerCallback mServerCallback = new IAutoFillServerCallback.Stub() {
+ @Override
+ public void showResponse(FillResponse response) {
+ // TODO(b/33197203): add MetricsLogger call
+ if (response == null) {
+ if (DEBUG) Slog.d(TAG, "showResponse(): null response");
+
+ removeSelf();
+ return;
+ }
+
+ synchronized (mLock) {
+ showResponseLocked(response, response.isAuthRequired());
+ }
+ }
+
+ @Override
+ public void showError(CharSequence message) {
+ // TODO(b/33197203): add MetricsLogger call
+ if (DEBUG) Slog.d(TAG, "showError(): " + message);
+
+ mUi.showError(message);
+
+ removeSelf();
+ }
+
+ @Override
+ public void highlightSavedFields(AutoFillId[] ids) {
+ // TODO(b/33197203): add MetricsLogger call
+ if (DEBUG) Slog.d(TAG, "highlightSavedFields(): " + Arrays.toString(ids));
+
+ mUi.highlightSavedFields(ids);
+
+ removeSelf();
+ }
+
+ @Override
+ public void unlockFillResponse(int flags) {
+ // TODO(b/33197203): add proper MetricsLogger calls?
+ if (DEBUG) Log.d(TAG, "unlockUser(): flags=" + flags);
+
+ synchronized (mLock) {
+ if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) {
+ if (mResponseRequiringAuth == null) {
+ Log.wtf(TAG, "unlockUser(): no mResponseRequiringAuth on flags "
+ + flags);
+ removeSelf();
+ return;
+ }
+ final List<Dataset> datasets = mResponseRequiringAuth.getDatasets();
+ if (datasets.isEmpty()) {
+ Log.w(TAG, "unlockUser(): no dataset on previous response: "
+ + mResponseRequiringAuth);
+ removeSelf();
+ return;
+ }
+ mAutoFillDirectly = true;
+ showResponseLocked(mResponseRequiringAuth, false);
+ }
+ // TODO(b/33197203): show UI error on authentication failure?
+ // Or let service handle it?
+ }
+ }
+
+ @Override
+ public void unlockDataset(Dataset dataset, int flags) {
+ // TODO(b/33197203): add proper MetricsLogger calls?
+ if (DEBUG) Log.d(TAG, "unlockDataset(): dataset=" + dataset + ", flags=" + flags);
+
+ if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) {
+ autoFillAppLocked(dataset != null ? dataset : mDatasetRequiringAuth, true);
+ return;
+ }
+ removeSelf();
+ }
+ };
+
+ private Session(int id, Bundle extras) {
+ this.mId = id;
+ this.mExtras = extras;
+ this.mFingerprintService = IFingerprintService.Stub
+ .asInterface(ServiceManager.getService("fingerprint"));
}
- @Override
- public void showResponse(FillResponse response) {
- // TODO(b/33197203): add MetricsLogger call
- if (DEBUG) Slog.d(TAG, "showResponse(): " + response);
+ private void showResponseLocked(FillResponse response, boolean authRequired) {
+ if (DEBUG) Slog.d(TAG, "showResponse(directly=" + mAutoFillDirectly
+ + ", authRequired=" + authRequired +"):" + response);
- mUi.showOptions(mUserId, id, response);
+ if (mAutoFillDirectly && response != null) {
+ final List<Dataset> datasets = response.getDatasets();
+ if (datasets.size() == 1) {
+ // User authenticated and provider returned just 1 dataset - auto-fill it now!
+ final Dataset dataset = datasets.get(0);
+ if (DEBUG) Slog.d(TAG, "auto-filling directly from auth: " + dataset);
+
+ autoFillAppLocked(dataset, true);
+ return;
+ }
+ }
+
+ if (!authRequired) {
+ // TODO(b/33197203): add MetricsLogger call
+ mUi.showOptions(mUserId, mId, response);
+ return;
+ }
+
+ // Handles response that requires authentication.
+ // TODO(b/33197203): add MetricsLogger call, including if fingerprint requested
+
+ mResponseRequiringAuth = response;
+ final boolean requiresFingerprint = response.hasCryptoObject();
+ if (requiresFingerprint) {
+ // TODO(b/33197203): check if fingerprint is available first and call error callback
+ // with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
+ // Start scanning for the fingerprint.
+ scanFingerprint(response.getCryptoObjectOpId());
+ }
+ // Displays the message asking the user to tap (or fingerprint) for AutoFill.
+ mUi.showFillResponseAuthenticationRequest(mUserId, mId, requiresFingerprint,
+ response.getExtras(), response.getFlags());
}
- @Override
- public void showError(String message) {
- // TODO(b/33197203): add MetricsLogger call
- if (DEBUG) Slog.d(TAG, "showError(): " + message);
+ void autoFill(Dataset dataset) {
+ synchronized (mLock) {
+ // Autofill it directly...
+ if (!dataset.isAuthRequired()) {
+ autoFillAppLocked(dataset, true);
+ return;
+ }
- mUi.showError(message);
+ // ...or handle authentication.
- removeSelf();
+ mDatasetRequiringAuth = dataset;
+ final boolean requiresFingerprint = dataset.hasCryptoObject();
+ if (requiresFingerprint) {
+ // TODO(b/33197203): check if fingerprint is available first and call error callback
+ // with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
+ // Start scanning for the fingerprint.
+ scanFingerprint(dataset.getCryptoObjectOpId());
+ // Displays the message asking the user to tap (or fingerprint) for AutoFill.
+ mUi.showDatasetFingerprintAuthenticationRequest(dataset);
+ } else {
+ try {
+ mService.authenticateDataset(dataset.getExtras(),
+ FLAG_AUTHENTICATION_REQUESTED);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error authenticating dataset: " + e);
+ }
+ }
+ }
}
- @Override
- public void highlightSavedFields(AutoFillId[] ids) {
+ private void autoFillAppLocked(Dataset dataset, boolean removeSelf) {
+ try {
+ if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
+ mAppCallback.autoFill(dataset);
+
+ // TODO(b/33197203): temporarily hack: show the save notification, since save is
+ // not integrated with IME yet.
+ mUi.showSaveNotification(mUserId, null, dataset);
+
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error auto-filling activity: " + e);
+ }
+ if (removeSelf) {
+ removeSelf();
+ }
+ }
+
+ private void scanFingerprint(long opId) {
// TODO(b/33197203): add MetricsLogger call
- if (DEBUG) Slog.d(TAG, "showSaved(): " + Arrays.toString(ids));
+ if (DEBUG) Slog.d(TAG, "Starting fingerprint scan for op id: " + opId);
- mUi.highlightSavedFields(ids);
-
- removeSelf();
+ // TODO(b/33197203): since we're clearing the AutoFillService's identity, make sure
+ // this method is only called at the proper times, otherwise a malicious provider could
+ // keep the callback refence to bypass the check
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // TODO(b/33197203): set a timeout?
+ mFingerprintService.authenticate(mToken, opId, mUserId, mServiceReceiver, 0, null);
+ } catch (RemoteException e) {
+ // Local call, shouldn't happen.
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private void removeSelf() {
synchronized (mLock) {
- removeServerCallbackLocked(id);
+ removeSessionLocked(mId);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index 6fafd5d..ad525d4 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -15,15 +15,18 @@
*/
package com.android.server.autofill;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
-import static com.android.server.autofill.AutoFillManagerService.DEBUG;
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.bundleToString;
import android.app.Activity;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -31,7 +34,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.service.autofill.AutoFillService;
-import android.util.Log;
import android.util.Slog;
import android.view.autofill.AutoFillId;
import android.view.autofill.Dataset;
@@ -43,8 +45,6 @@
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
-import java.util.Set;
/**
* Handles all auto-fill related UI tasks.
@@ -67,7 +67,7 @@
/**
* Displays an error message to the user.
*/
- void showError(String message) {
+ void showError(CharSequence message) {
// TODO(b/33197203): proper implementation
UiThread.getHandler().runWithScissors(() -> {
Toast.makeText(mContext, "AutoFill error: " + message, Toast.LENGTH_LONG).show();
@@ -89,10 +89,115 @@
* Shows the options from a {@link FillResponse} so the user can pick up the proper
* {@link Dataset} (when the response has one).
*/
- void showOptions(int userId, int callbackId, FillResponse response) {
+ void showOptions(int userId, int sessionId, FillResponse response) {
// TODO(b/33197203): proper implementation
// TODO(b/33197203): make sure if removes the callback from cache
- showOptionsNotification(userId, callbackId, response);
+ showOptionsNotification(userId, sessionId, response);
+ }
+
+ /**
+ * Shows an UI affordance indicating that user action is required before a {@link FillResponse}
+ * can be used.
+ *
+ * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint or tap to
+ * autofill" or "Tap to autofill", depending on the value of {@code usesFingerprint}.
+ */
+ void showFillResponseAuthenticationRequest(int userId, int sessionId, boolean usesFingerprint,
+ Bundle extras, int flags) {
+ // TODO(b/33197203): proper implementation
+ showAuthNotification(userId, sessionId, usesFingerprint, extras, flags);
+ }
+
+ /**
+ * Shows an UI affordance asking indicating that user action is required before a
+ * {@link Dataset} can be used.
+ *
+ * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint to
+ * autofill".
+ */
+ void showDatasetFingerprintAuthenticationRequest(Dataset dataset) {
+ if (DEBUG) Slog.d(TAG, "showDatasetAuthenticationRequest(): dataset=" + dataset);
+
+ // TODO(b/33197203): proper implementation (either pop up a fingerprint dialog or replace
+ // the auto-fill bar with a new message.
+ UiThread.getHandler().runWithScissors(() -> {
+ Toast.makeText(mContext, "AutoFill: press fingerprint to unlock " + dataset.getName(),
+ Toast.LENGTH_LONG).show();
+ }, 0);
+ }
+
+ /**
+ * Called by service after the user user the fingerprint sensors to authenticate.
+ */
+ void dismissFingerprintRequest(int userId, boolean success) {
+ if (DEBUG) Slog.d(TAG, "dismissFingerprintRequest(): ok=" + success);
+
+ dismissAuthNotification(userId);
+
+ if (!success) {
+ // TODO(b/33197203): proper implementation (snack bar / i18n string)
+ UiThread.getHandler().runWithScissors(() -> {
+ Toast.makeText(mContext, "AutoFill: fingerprint failed", Toast.LENGTH_LONG).show();
+ }, 0);
+ }
+ }
+
+ private AutoFillManagerServiceImpl getServiceLocked(int userId) {
+ final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "no auto-fill service for user " + userId);
+ }
+ return service;
+ }
+
+ private void onSaveRequested(int userId, Bundle responseExtras, Bundle datasetExtras) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+ if (service == null) return;
+
+ final Bundle extras = (responseExtras == null && datasetExtras == null)
+ ? null : new Bundle();
+
+ if (responseExtras != null) {
+ if (DEBUG) Slog.d(TAG, "response extras on save notification: " +
+ bundleToString(responseExtras));
+ extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
+ }
+ if (datasetExtras != null) {
+ if (DEBUG) Slog.d(TAG, "dataset extras on save notificataion: " +
+ bundleToString(datasetExtras));
+ extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
+ }
+
+ service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_SAVE);
+ }
+ }
+
+ private void onDatasetPicked(int userId, Dataset dataset, int sessionId) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+ if (service == null) return;
+
+ service.autoFillApp(sessionId, dataset);
+ }
+ }
+
+ private void onSessionDone(int userId, int sessionId) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+ if (service == null) return;
+
+ service.removeSessionLocked(sessionId);
+ }
+ }
+
+ private void onResponseAuthenticationRequested(int userId, Bundle extras, int flags) {
+ synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+ if (service == null) return;
+
+ service.notifyResponseAuthenticationResult(extras, flags);
+ }
}
/////////////////////////////////////////////////////////////////////////////////
@@ -107,14 +212,17 @@
// Extras used in the notification intents
private static final String EXTRA_USER_ID = "user_id";
private static final String EXTRA_NOTIFICATION_TYPE = "notification_type";
- private static final String EXTRA_CALLBACK_ID = "callback_id";
+ private static final String EXTRA_SESSION_ID = "session_id";
private static final String EXTRA_FILL_RESPONSE = "fill_response";
private static final String EXTRA_DATASET = "dataset";
+ private static final String EXTRA_AUTH_REQUIRED_EXTRAS = "auth_required_extras";
+ private static final String EXTRA_FLAGS = "flags";
private static final String TYPE_OPTIONS = "options";
- private static final String TYPE_DELETE_CALLBACK = "delete_callback";
+ private static final String TYPE_FINISH_SESSION = "finish_session";
private static final String TYPE_PICK_DATASET = "pick_dataset";
private static final String TYPE_SAVE = "save";
+ private static final String TYPE_AUTH_RESPONSE = "auth_response";
@GuardedBy("mLock")
private BroadcastReceiver mNotificationReceiver;
@@ -139,68 +247,46 @@
@Override
public void onReceive(Context context, Intent intent) {
final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
+ final int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+ final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
+ if (type == null) {
+ Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
+ return;
+ }
+ final FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
+ final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
+ final Bundle responseExtras = response == null ? null : response.getExtras();
+ final Bundle datasetExtras = dataset == null ? null : dataset.getExtras();
+ final int flags = intent.getIntExtra(EXTRA_FLAGS, 0);
+ if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
+ + ", sessionId=" + sessionId);
synchronized (mLock) {
- final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
- if (service == null) {
- Slog.w(TAG, "no auto-fill service for user " + userId);
- return;
- }
-
- final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1);
- final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
- if (type == null) {
- Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
- return;
- }
- final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
- final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
- final Bundle datasetArgs = dataset == null ? null : dataset.getExtras();
- final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras();
-
- // Bundle sent on AutoFillService methods - only set if service provided a bundle
- final Bundle extras = (datasetArgs == null && fillDataArgs == null)
- ? null : new Bundle();
-
- if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
- + ", callbackId=" + callbackId);
switch (type) {
case TYPE_SAVE:
- if (datasetArgs != null) {
- if (DEBUG) Log.d(TAG, "filldata args on save notificataion: " +
- bundleToString(fillDataArgs));
- extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, fillDataArgs);
- }
- if (dataset != null) {
- if (DEBUG) Log.d(TAG, "dataset args on save notificataion: " +
- bundleToString(datasetArgs));
- extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetArgs);
- }
- service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_SAVE);
+ onSaveRequested(userId, responseExtras, datasetExtras);
break;
- case TYPE_DELETE_CALLBACK:
- service.removeServerCallbackLocked(callbackId);
+ case TYPE_FINISH_SESSION:
+ onSessionDone(userId, sessionId);
break;
case TYPE_PICK_DATASET:
- service.autoFillApp(callbackId, dataset);
+ onDatasetPicked(userId, dataset, sessionId);
+
// Must cancel notification because it might be comming from action
- if (DEBUG) Log.d(TAG, "Cancelling notification");
+ if (DEBUG) Slog.d(TAG, "Cancelling notification");
NotificationManager.from(mContext).cancel(TYPE_OPTIONS, userId);
- if (datasetArgs != null) {
- if (DEBUG) Log.d(TAG, "adding dataset's extra_data on save intent: "
- + bundleToString(datasetArgs));
- extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetArgs);
- }
-
- // Also show notification with option to save the data
- showSaveNotification(userId, fillData, dataset);
+ break;
+ case TYPE_AUTH_RESPONSE:
+ onResponseAuthenticationRequested(userId,
+ intent.getBundleExtra(EXTRA_AUTH_REQUIRED_EXTRAS), flags);
break;
default: {
Slog.w(TAG, "Unknown notification type: " + type);
}
}
}
+ collapseStatusBar();
}
}
@@ -211,66 +297,45 @@
return intent;
}
- private PendingIntent newPickDatasetPI(int userId, int callbackId, FillResponse response,
+ private PendingIntent newPickDatasetPI(int userId, int sessionId, FillResponse response,
Dataset dataset) {
final int resultCode = ++ sResultCode;
- if (DEBUG) Log.d(TAG, "newPickDatasetPI: userId=" + userId + ", callback=" + callbackId
+ if (DEBUG) Slog.d(TAG, "newPickDatasetPI: userId=" + userId + ", sessionId=" + sessionId
+ ", resultCode=" + resultCode);
final Intent intent = newNotificationIntent(userId, TYPE_PICK_DATASET);
- intent.putExtra(EXTRA_CALLBACK_ID, callbackId);
+ intent.putExtra(EXTRA_SESSION_ID, sessionId);
intent.putExtra(EXTRA_FILL_RESPONSE, response);
intent.putExtra(EXTRA_DATASET, dataset);
return PendingIntent.getBroadcast(mContext, resultCode, intent,
PendingIntent.FLAG_ONE_SHOT);
}
- private static String bundleToString(Bundle bundle) {
- if (bundle == null) {
- return "null";
- }
- final Set<String> keySet = bundle.keySet();
- final StringBuilder builder = new StringBuilder("[Bundle with ").append(keySet.size())
- .append(" keys:");
- for (String key : keySet) {
- final Object value = bundle.get(key);
- builder.append(' ').append(key).append('=');
- builder.append((value instanceof Object[])
- ? Arrays.toString((Objects[]) value) : value);
- }
- return builder.append(']').toString();
- }
-
/**
* Shows a notification with the results of an auto-fill request, using notications actions
* to emulate the auto-fill bar buttons displaying the dataset names.
*/
- private void showOptionsNotification(int userId, int callbackId, FillResponse response) {
+ private void showOptionsNotification(int userId, int sessionId, FillResponse response) {
final long token = Binder.clearCallingIdentity();
try {
- showOptionsNotificationAsSystem(userId, callbackId, response);
+ showOptionsNotificationAsSystem(userId, sessionId, response);
} finally {
Binder.restoreCallingIdentity(token);
}
}
- private void showOptionsNotificationAsSystem(int userId, int callbackId,
+ private void showOptionsNotificationAsSystem(int userId, int sessionId,
FillResponse response) {
// Make sure server callback is removed from cache if user cancels the notification.
- final Intent deleteIntent = newNotificationIntent(userId, TYPE_DELETE_CALLBACK);
- deleteIntent.putExtra(EXTRA_CALLBACK_ID, callbackId);
+ final Intent deleteIntent = newNotificationIntent(userId, TYPE_FINISH_SESSION)
+ .putExtra(EXTRA_SESSION_ID, sessionId);
final PendingIntent deletePendingIntent = PendingIntent.getBroadcast(mContext,
++sResultCode, deleteIntent, PendingIntent.FLAG_ONE_SHOT);
final String title = "AutoFill Options";
- final Notification.Builder notification = new Notification.Builder(mContext)
- .setCategory(Notification.CATEGORY_SYSTEM)
+ final Notification.Builder notification = newNotificationBuilder()
.setOngoing(false)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setLocalOnly(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
.setDeleteIntent(deletePendingIntent)
.setContentTitle(title);
@@ -305,10 +370,19 @@
autoCancel = false;
final int size = datasets.size();
subTitle = "There are " + size + " option(s).\n"
- + "Use the notification action(s) to select the proper one.";
+ + "Use the notification action(s) to select the proper one."
+ + "Actions with (F) require fingerprint unlock, and with (P) require"
+ + "provider authentication to unlock";
for (Dataset dataset : datasets) {
- final CharSequence name = dataset.getName();
- final PendingIntent pi = newPickDatasetPI(userId, callbackId, response, dataset);
+ final StringBuilder name = new StringBuilder(dataset.getName());
+ if (dataset.isAuthRequired()) {
+ if (dataset.hasCryptoObject()) {
+ name.append("(F)");
+ } else {
+ name.append("(P)");
+ }
+ }
+ final PendingIntent pi = newPickDatasetPI(userId, sessionId, response, dataset);
notification.addAction(new Action.Builder(null, name, pi).build());
}
}
@@ -324,9 +398,20 @@
}
}
- private void showSaveNotification(int userId, FillResponse response, Dataset dataset) {
+ void showSaveNotification(int userId, FillResponse response, Dataset dataset) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ showSaveNotificationAsSystem(userId, response, dataset);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void showSaveNotificationAsSystem(int userId, FillResponse response, Dataset dataset) {
final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE);
- saveIntent.putExtra(EXTRA_FILL_RESPONSE, response);
+ if (response != null) {
+ saveIntent.putExtra(EXTRA_FILL_RESPONSE, response);
+ }
if (dataset != null) {
saveIntent.putExtra(EXTRA_DATASET, dataset);
}
@@ -334,17 +419,15 @@
++sResultCode, saveIntent, PendingIntent.FLAG_ONE_SHOT);
final String title = "AutoFill Save";
- final String subTitle = "Tap notification to ask provider to save fields: \n"
- + Arrays.toString(response.getSavableIds());
+ // Response is not set after fillign an authenticated dataset...
+ final String subTitle = response == null
+ ? "Tap notification to ask provider to save fields."
+ : "Tap notification to ask provider to save fields: \n"
+ + Arrays.toString(response.getSavableIds());
- final Notification notification = new Notification.Builder(mContext)
- .setCategory(Notification.CATEGORY_SYSTEM)
+ final Notification notification = newNotificationBuilder()
.setAutoCancel(true)
.setOngoing(false)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setLocalOnly(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)
.setContentIntent(savePendingIntent)
.setStyle(new Notification.BigTextStyle().bigText(subTitle))
@@ -352,6 +435,68 @@
NotificationManager.from(mContext).notify(TYPE_SAVE, userId, notification);
}
+ private void showAuthNotification(int userId, int sessionId, boolean usesFingerprint,
+ Bundle extras, int flags) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ showAuthNotificationAsSystem(userId, sessionId, usesFingerprint, extras, flags);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void showAuthNotificationAsSystem(int userId, int sessionId,
+ boolean usesFingerprint, Bundle extras, int flags) {
+ final String title = "AutoFill Authentication";
+ final StringBuilder subTitle = new StringBuilder("Provider require user authentication.\n");
+
+ final Intent authIntent = newNotificationIntent(userId, TYPE_AUTH_RESPONSE)
+ .putExtra(EXTRA_SESSION_ID, sessionId);
+ if (extras != null) {
+ authIntent.putExtra(EXTRA_AUTH_REQUIRED_EXTRAS, extras);
+ }
+ if (flags != 0) {
+ authIntent.putExtra(EXTRA_FLAGS, flags);
+ }
+ final PendingIntent authPendingIntent = PendingIntent.getBroadcast(mContext, ++sResultCode,
+ authIntent, PendingIntent.FLAG_ONE_SHOT);
+
+ if (usesFingerprint) {
+ subTitle.append("But kindly accepts your fingerprint instead"
+ + "\n(tap fingerprint sensor to trigger it)");
+
+ } else {
+ subTitle.append("Tap notification to launch its authentication UI.");
+ }
+
+ final Notification.Builder notification = newNotificationBuilder()
+ .setAutoCancel(true)
+ .setOngoing(false)
+ .setContentTitle(title)
+ .setStyle(new Notification.BigTextStyle().bigText(subTitle.toString()));
+ if (authPendingIntent != null) {
+ notification.setContentIntent(authPendingIntent);
+ }
+ NotificationManager.from(mContext).notify(TYPE_AUTH_RESPONSE, userId, notification.build());
+ }
+
+ private void dismissAuthNotification(int userId) {
+ NotificationManager.from(mContext).cancel(TYPE_AUTH_RESPONSE, userId);
+ }
+
+ private Notification.Builder newNotificationBuilder() {
+ return new Notification.Builder(mContext)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setLocalOnly(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ }
+
+ private void collapseStatusBar() {
+ final StatusBarManager sbm = (StatusBarManager) mContext.getSystemService("statusbar");
+ sbm.collapsePanels();
+ }
/////////////////////////////////////////
// End of temporary notification code. //
/////////////////////////////////////////
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
new file mode 100644
index 0000000..79095a1
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.os.Bundle;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+
+final class Helper {
+
+ static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
+ static final String REDACTED = "[REDACTED]";
+
+ static void append(StringBuilder builder, Bundle bundle) {
+ if (bundle == null) {
+ builder.append("N/A");
+ } else if (!DEBUG) {
+ builder.append(REDACTED);
+ } else {
+ final Set<String> keySet = bundle.keySet();
+ builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
+ for (String key : keySet) {
+ final Object value = bundle.get(key);
+ builder.append(' ').append(key).append('=');
+ builder.append((value instanceof Object[])
+ ? Arrays.toString((Objects[]) value) : value);
+ }
+ builder.append(']');
+ }
+ }
+
+ static String bundleToString(Bundle bundle) {
+ final StringBuilder builder = new StringBuilder();
+ append(builder, bundle);
+ return builder.toString();
+ }
+
+ private Helper() {
+ throw new UnsupportedOperationException("contains static members only");
+ }
+}
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index dab4dfb..8d37b0f 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -60,6 +60,7 @@
import android.provider.Settings;
import android.provider.Settings.Global;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.TimedRemoteCaller;
@@ -77,12 +78,13 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
-import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
/**
* Backing service for {@link android.net.NetworkScoreManager}.
@@ -91,6 +93,7 @@
public class NetworkScoreService extends INetworkScoreService.Stub {
private static final String TAG = "NetworkScoreService";
private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
private final Context mContext;
private final NetworkScorerAppManager mNetworkScorerAppManager;
@@ -407,7 +410,7 @@
}
final BiConsumer<INetworkScoreCache, Object> consumer =
- new FilteringCacheUpdatingConsumer(mContext, entry.getValue(),
+ FilteringCacheUpdatingConsumer.create(mContext, entry.getValue(),
entry.getKey());
sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
}
@@ -424,25 +427,28 @@
* accepted {@link INetworkScoreCache} implementation.
*/
@VisibleForTesting
- public static class FilteringCacheUpdatingConsumer
+ static class FilteringCacheUpdatingConsumer
implements BiConsumer<INetworkScoreCache, Object> {
private final Context mContext;
private final List<ScoredNetwork> mScoredNetworkList;
private final int mNetworkType;
- // TODO(jjoslin): 1/23/17 - Consider a Map if we implement more filters.
- private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
- private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;
+ // TODO: 1/23/17 - Consider a Map if we implement more filters.
+ // These are created on-demand to defer the construction cost until
+ // an instance is actually needed.
+ private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
+ private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
- public FilteringCacheUpdatingConsumer(Context context,
+ static FilteringCacheUpdatingConsumer create(Context context,
List<ScoredNetwork> scoredNetworkList, int networkType) {
- this(context, scoredNetworkList, networkType, null, null);
+ return new FilteringCacheUpdatingConsumer(context, scoredNetworkList, networkType,
+ null, null);
}
@VisibleForTesting
- public FilteringCacheUpdatingConsumer(Context context,
+ FilteringCacheUpdatingConsumer(Context context,
List<ScoredNetwork> scoredNetworkList, int networkType,
- Function<List<ScoredNetwork>, List<ScoredNetwork>> currentNetworkFilter,
- Function<List<ScoredNetwork>, List<ScoredNetwork>> scanResultsFilter) {
+ UnaryOperator<List<ScoredNetwork>> currentNetworkFilter,
+ UnaryOperator<List<ScoredNetwork>> scanResultsFilter) {
mContext = context;
mScoredNetworkList = scoredNetworkList;
mNetworkType = networkType;
@@ -461,11 +467,10 @@
final List<ScoredNetwork> filteredNetworkList =
filterScores(mScoredNetworkList, filterType);
if (!filteredNetworkList.isEmpty()) {
- networkScoreCache.updateScores(
- Collections.unmodifiableList(filteredNetworkList));
+ networkScoreCache.updateScores(filteredNetworkList);
}
} catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
}
}
@@ -554,10 +559,8 @@
* the computation is only done once.
*/
@VisibleForTesting
- public static class CurrentNetworkScoreCacheFilter
- implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
+ static class CurrentNetworkScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
private final NetworkKey mCurrentNetwork;
- private Pair<List<ScoredNetwork>, Integer> mCache;
CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
@@ -569,25 +572,14 @@
return Collections.emptyList();
}
- final int inputListHash = scoredNetworks.hashCode();
- if (mCache == null || mCache.second != inputListHash) {
- ScoredNetwork currentScore = null;
- for (int i = 0; i < scoredNetworks.size(); i++) {
- final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
- if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
- currentScore = scoredNetwork;
- break;
- }
- }
-
- if (currentScore == null) {
- mCache = Pair.create(Collections.emptyList(), inputListHash);
- } else {
- mCache = Pair.create(Collections.singletonList(currentScore), inputListHash);
+ for (int i = 0; i < scoredNetworks.size(); i++) {
+ final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+ if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
+ return Collections.singletonList(scoredNetwork);
}
}
- return mCache.first;
+ return Collections.emptyList();
}
}
@@ -602,15 +594,14 @@
* times in a row the computation is only done once.
*/
@VisibleForTesting
- public static class ScanResultsScoreCacheFilter
- implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
- private final List<NetworkKey> mScanResultKeys;
- private Pair<List<ScoredNetwork>, Integer> mCache;
+ static class ScanResultsScoreCacheFilter implements UnaryOperator<List<ScoredNetwork>> {
+ private final Set<NetworkKey> mScanResultKeys;
ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
- mScanResultKeys = new ArrayList<>();
List<ScanResult> scanResults = resultsSupplier.get();
- for (int i = 0; i < scanResults.size(); i++) {
+ final int size = scanResults.size();
+ mScanResultKeys = new ArraySet<>(size);
+ for (int i = 0; i < size; i++) {
ScanResult scanResult = scanResults.get(i);
mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult));
}
@@ -622,22 +613,15 @@
return Collections.emptyList();
}
- final int inputListHash = scoredNetworks.hashCode();
- if (mCache == null || mCache.second != inputListHash) {
- List<ScoredNetwork> filteredScores = new ArrayList<>();
- for (int i = 0; i < scoredNetworks.size(); i++) {
- final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
- for (int j = 0; j < mScanResultKeys.size(); j++) {
- final NetworkKey scanResultKey = mScanResultKeys.get(j);
- if (scanResultKey.equals(scoredNetwork.networkKey)) {
- filteredScores.add(scoredNetwork);
- }
- }
+ List<ScoredNetwork> filteredScores = new ArrayList<>();
+ for (int i = 0; i < scoredNetworks.size(); i++) {
+ final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+ if (mScanResultKeys.contains(scoredNetwork.networkKey)) {
+ filteredScores.add(scoredNetwork);
}
- mCache = Pair.create(filteredScores, inputListHash);
}
- return mCache.first;
+ return filteredScores;
}
}
@@ -796,7 +780,7 @@
return caller.getRecommendationResult(provider, request);
} catch (RemoteException | TimeoutException e) {
Log.w(TAG, "Failed to request a recommendation.", e);
- // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ // TODO: 12/15/16 - Keep track of failures.
}
}
@@ -850,7 +834,7 @@
return;
} catch (RemoteException e) {
Log.w(TAG, "Failed to request a recommendation.", e);
- // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ // TODO: 12/15/16 - Keep track of failures.
// Remove the timeout message
mHandler.removeMessages(timeoutMsg.what, pair);
// Will fall through and send back the default recommendation.
@@ -873,12 +857,12 @@
if (provider != null) {
try {
provider.requestScores(networks);
- // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to
+ // TODO: 12/15/16 - Consider pushing null scores into the cache to
// prevent repeated requests for the same scores.
return true;
} catch (RemoteException e) {
Log.w(TAG, "Failed to request scores.", e);
- // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ // TODO: 12/15/16 - Keep track of failures.
}
}
return false;
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 1e0035d..6f49df4 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -30,7 +30,6 @@
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.Slog;
import java.util.Comparator;
import java.util.Objects;
@@ -57,7 +56,15 @@
@Override
public int compare(NotificationRecord left, NotificationRecord right) {
- // First up: sufficiently important ongoing notifications of certain categories
+ // first all colorized notifications
+ boolean leftImportantColorized = isImportantColorized(left);
+ boolean rightImportantColorized = isImportantColorized(right);
+
+ if (leftImportantColorized != rightImportantColorized) {
+ return -1 * Boolean.compare(leftImportantColorized, rightImportantColorized);
+ }
+
+ // sufficiently important ongoing notifications of certain categories
boolean leftImportantOngoing = isImportantOngoing(left);
boolean rightImportantOngoing = isImportantOngoing(right);
@@ -106,6 +113,13 @@
return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
}
+ private boolean isImportantColorized(NotificationRecord record) {
+ if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) {
+ return false;
+ }
+ return record.getNotification().isColorized();
+ }
+
private boolean isImportantOngoing(NotificationRecord record) {
if (!isOngoing(record)) {
return false;
@@ -148,7 +162,6 @@
return (record.getNotification().flags & ongoingFlags) != 0;
}
-
private Class<? extends Notification.Style> getNotificationStyle(NotificationRecord record) {
String templateClass =
record.getNotification().extras.getString(Notification.EXTRA_TEMPLATE);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 984120f..9d2d9e5 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -716,7 +716,7 @@
return null;
}
int parentUserId = profile.profileGroupId;
- if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+ if (parentUserId == userHandle || parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
return null;
} else {
return getUserInfoLU(parentUserId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7558e3c..5c15750 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8957,18 +8957,20 @@
ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
if (profileOwner != null) {
- return createShowAdminSupportIntent(profileOwner, userId);
+ return DevicePolicyManagerService.this
+ .createShowAdminSupportIntent(profileOwner, userId);
}
final Pair<Integer, ComponentName> deviceOwner =
mOwners.getDeviceOwnerUserIdAndComponent();
if (deviceOwner != null && deviceOwner.first == userId) {
- return createShowAdminSupportIntent(deviceOwner.second, userId);
+ return DevicePolicyManagerService.this
+ .createShowAdminSupportIntent(deviceOwner.second, userId);
}
// We're not specifying the device admin because there isn't one.
if (useDefaultIfNoAdmin) {
- return createShowAdminSupportIntent(null, userId);
+ return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId);
}
return null;
}
@@ -8996,11 +8998,12 @@
if (enforcedByDo && enforcedByPo) {
// In this case, we'll show an admin support dialog that does not
// specify the admin.
- return createShowAdminSupportIntent(null, userId);
+ return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId);
} else if (enforcedByPo) {
final ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
if (profileOwner != null) {
- return createShowAdminSupportIntent(profileOwner, userId);
+ return DevicePolicyManagerService.this
+ .createShowAdminSupportIntent(profileOwner, userId);
}
// This could happen if another thread has changed the profile owner since we called
// getUserRestrictionSource
@@ -9009,7 +9012,8 @@
final Pair<Integer, ComponentName> deviceOwner
= mOwners.getDeviceOwnerUserIdAndComponent();
if (deviceOwner != null) {
- return createShowAdminSupportIntent(deviceOwner.second, deviceOwner.first);
+ return DevicePolicyManagerService.this
+ .createShowAdminSupportIntent(deviceOwner.second, deviceOwner.first);
}
// This could happen if another thread has changed the device owner since we called
// getUserRestrictionSource
@@ -9017,15 +9021,57 @@
}
return null;
}
+ }
- private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
- // This method is called with AMS lock held, so don't take DPMS lock
- final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
- intent.putExtra(Intent.EXTRA_USER_ID, userId);
- intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
+ private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
+ // This method is called with AMS lock held, so don't take DPMS lock
+ final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
+ intent.putExtra(Intent.EXTRA_USER_ID, userId);
+ intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ @Override
+ public Intent createAdminSupportIntent(String restriction) {
+ Preconditions.checkNotNull(restriction);
+ final int uid = mInjector.binderGetCallingUid();
+ final int userId = UserHandle.getUserId(uid);
+ Intent intent = null;
+ if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction) ||
+ DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+ synchronized(this) {
+ final DevicePolicyData policy = getUserData(userId);
+ final int N = policy.mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ final ActiveAdmin admin = policy.mAdminList.get(i);
+ if ((admin.disableCamera &&
+ DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) ||
+ (admin.disableScreenCapture && DevicePolicyManager
+ .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
+ intent = createShowAdminSupportIntent(admin.info.getComponent(), userId);
+ break;
+ }
+ }
+ // For the camera, a device owner on a different user can disable it globally,
+ // so we need an additional check.
+ if (intent == null
+ && DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
+ final ActiveAdmin admin = getDeviceOwnerAdminLocked();
+ if (admin != null && admin.disableCamera) {
+ intent = createShowAdminSupportIntent(admin.info.getComponent(),
+ mOwners.getDeviceOwnerUserId());
+ }
+ }
+ }
+ } else {
+ // if valid, |restriction| can only be a user restriction
+ intent = mLocalService.createUserRestrictionSupportIntent(userId, restriction);
}
+ if (intent != null) {
+ intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, restriction);
+ }
+ return intent;
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 4ca29cd..152b9c9 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -106,7 +106,7 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
+import java.util.function.UnaryOperator;
/**
* Tests for {@link NetworkScoreService}.
@@ -132,8 +132,8 @@
@Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
@Mock private IBinder mIBinder, mIBinder2;
@Mock private INetworkRecommendationProvider mRecommendationProvider;
- @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
- @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;
+ @Mock private UnaryOperator<List<ScoredNetwork>> mCurrentNetworkFilter;
+ @Mock private UnaryOperator<List<ScoredNetwork>> mScanResultsFilter;
@Mock private WifiInfo mWifiInfo;
@Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 5d4c3cf..c29668f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1950,6 +1950,81 @@
}
}
+ public void testCreateAdminSupportIntent() throws Exception {
+ // Setup device owner.
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+
+ // Nonexisting permission returns null
+ Intent intent = dpm.createAdminSupportIntent("disallow_nothing");
+ assertNull(intent);
+
+ // Existing permission that is not set returns null
+ intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+ assertNull(intent);
+
+ // Existing permission that is not set by device/profile owner returns null
+ when(mContext.userManager.hasUserRestriction(
+ eq(UserManager.DISALLOW_ADJUST_VOLUME),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(true);
+ intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+ assertNull(intent);
+
+ // Permission that is set by device owner returns correct intent
+ when(mContext.userManager.getUserRestrictionSource(
+ eq(UserManager.DISALLOW_ADJUST_VOLUME),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+ intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+ assertNotNull(intent);
+ assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, intent.getAction());
+ assertEquals(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID),
+ intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
+ assertEquals(admin1,
+ (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN));
+ assertEquals(UserManager.DISALLOW_ADJUST_VOLUME,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+ // Try with POLICY_DISABLE_CAMERA and POLICY_DISABLE_SCREEN_CAPTURE, which are not
+ // user restrictions
+
+ // Camera is not disabled
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_CAMERA);
+ assertNull(intent);
+
+ // Camera is disabled
+ dpm.setCameraDisabled(admin1, true);
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_CAMERA);
+ assertNotNull(intent);
+ assertEquals(DevicePolicyManager.POLICY_DISABLE_CAMERA,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+ // Screen capture is not disabled
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ assertNull(intent);
+
+ // Screen capture is disabled
+ dpm.setScreenCaptureDisabled(admin1, true);
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ assertNotNull(intent);
+ assertEquals(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+ // Same checks for different user
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ // Camera should be disabled by device owner
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_CAMERA);
+ assertNotNull(intent);
+ assertEquals(DevicePolicyManager.POLICY_DISABLE_CAMERA,
+ intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+ assertEquals(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID),
+ intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
+ // ScreenCapture should not be disabled by device owner
+ intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ assertNull(intent);
+ }
+
/**
* Test for:
* {@link DevicePolicyManager#setAffiliationIds}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 9b2c94e..1964cad 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -177,10 +177,12 @@
UserInfo userInfo = createProfileForUser("Profile",
UserInfo.FLAG_MANAGED_PROFILE, primaryUserId);
assertNotNull(userInfo);
-
+ assertNull(mUserManager.getProfileParent(primaryUserId));
UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
assertNotNull(parentProfileInfo);
assertEquals(parentProfileInfo.id, primaryUserId);
+ removeUser(userInfo.id);
+ assertNull(mUserManager.getProfileParent(primaryUserId));
}
// Make sure only one managed profile can be created
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a853d5c..cd62cd5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -259,6 +259,12 @@
KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
/**
+ * Override the device's configuration for the ImsService to use for this SIM card.
+ */
+ public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING =
+ "config_ims_package_override_string";
+
+ /**
* Override the platform's notion of a network operator being considered roaming.
* Value is string array of SIDs to be considered roaming for 3GPP2 RATs.
*/
@@ -1396,6 +1402,7 @@
});
sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
+ sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b28627b..aff5db2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -373,13 +373,46 @@
"android.telephony.action.EMERGENCY_ASSISTANCE";
/**
+ * A boolean meta-data value indicating whether the voicemail settings should be hidden in the
+ * call settings page launched by
+ * {@link android.telecom.TelecomManager#ACTION_SHOW_CALL_SETTINGS}.
+ * Dialer implementations (see {@link android.telecom.TelecomManager#getDefaultDialerPackage()})
+ * which would also like to manage voicemail settings should set this meta-data to {@code true}
+ * in the manifest registration of their application.
+ *
+ * @see android.telecom.TelecomManager#ACTION_SHOW_CALL_SETTINGS
+ * @see #ACTION_CONFIGURE_VOICEMAIL
+ * @see #EXTRA_HIDE_PUBLIC_SETTINGS
+ */
+ public static final String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU =
+ "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
+
+ /**
* Open the voicemail settings activity to make changes to voicemail configuration.
+ *
+ * <p>
+ * The {@link #EXTRA_HIDE_PUBLIC_SETTINGS} hides settings the dialer will modify through public
+ * API if set.
+ *
+ * @see #EXTRA_HIDE_PUBLIC_SETTINGS
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CONFIGURE_VOICEMAIL =
"android.telephony.action.CONFIGURE_VOICEMAIL";
/**
+ * The boolean value indicating whether the voicemail settings activity launched by {@link
+ * #ACTION_CONFIGURE_VOICEMAIL} should hide settings accessible through public API. This is
+ * used by dialer implementations which provides their own voicemail settings UI, but still
+ * needs to expose device specific voicemail settings to the user.
+ *
+ * @see #ACTION_CONFIGURE_VOICEMAIL
+ * @see #METADATA_HIDE_VOICEMAIL_SETTINGS_MENU
+ */
+ public static final String EXTRA_HIDE_PUBLIC_SETTINGS =
+ "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
+
+ /**
* @hide
*/
public static final boolean EMERGENCY_ASSISTANCE_ENABLED = true;
@@ -2657,6 +2690,28 @@
return false;
}
+
+ /**
+ * Returns the package responsible of processing visual voicemail for the phone account.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
+ * READ_PHONE_STATE}
+ */
+ @Nullable
+ public String getVisualVoicemailPackageName(PhoneAccountHandle phoneAccountHandle) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony
+ .getVisualVoicemailPackageName(mContext.getOpPackageName(),
+ phoneAccountHandle);
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ return null;
+ }
+
/**
* Enables the visual voicemail SMS filter for a phone account. When the filter is
* enabled, Incoming SMS messages matching the OMTP VVM SMS interface will be redirected to the
diff --git a/telephony/java/android/telephony/ims/ImsServiceBase.java b/telephony/java/android/telephony/ims/ImsServiceBase.java
new file mode 100644
index 0000000..0b50eca
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsServiceBase.java
@@ -0,0 +1,35 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Base ImsService Implementation, which is used by the ImsResolver to bind.
+ * @hide
+ */
+@SystemApi
+public class ImsServiceBase extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
new file mode 100644
index 0000000..0509d60
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -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
+ */
+
+package android.telephony.ims.feature;
+
+/**
+ * Base class for all IMS features that are supported by the framework.
+ * @hide
+ */
+public class ImsFeature {
+
+ // Invalid feature value
+ public static final int INVALID = -1;
+ // ImsFeatures that are defined in the Manifests
+ public static final int EMERGENCY_MMTEL = 0;
+ public static final int MMTEL = 1;
+ public static final int RCS = 2;
+ // Total number of features defined
+ public static final int MAX = 3;
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
new file mode 100644
index 0000000..fa86a43
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.ims.internal;
+
+/**
+ * {@hide}
+ */
+interface IImsServiceController {
+ void createImsFeature(int slotId, int feature);
+ void removeImsFeature(int slotId, int feature);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
new file mode 100644
index 0000000..0a36b6b
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.ims.internal;
+
+/**
+ * {@hide}
+ */
+oneway interface IImsServiceFeatureListener {
+ void imsFeatureCreated(int slotId, int feature);
+ void imsFeatureRemoved(int slotId, int feature);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9a9a092..ec419d7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -478,6 +478,9 @@
boolean isVisualVoicemailEnabled(String callingPackage,
in PhoneAccountHandle accountHandle);
+ String getVisualVoicemailPackageName(String callingPackage,
+ in PhoneAccountHandle phoneAccountHandle);
+
// Not oneway, caller needs to make sure the vaule is set before receiving a SMS
void enableVisualVoicemailSmsFilter(String callingPackage, int subId,
in VisualVoicemailSmsFilterSettings settings);