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);