Merge "Profile owners on a user can communicate with device owners"
diff --git a/Android.mk b/Android.mk
index 63ad83f..43dbef6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -148,6 +148,7 @@
 	core/java/android/content/ISyncContext.aidl \
 	core/java/android/content/ISyncServiceAdapter.aidl \
 	core/java/android/content/ISyncStatusObserver.aidl \
+	core/java/android/content/om/IOverlayManager.aidl \
 	core/java/android/content/pm/ILauncherApps.aidl \
 	core/java/android/content/pm/IOnAppsChangedListener.aidl \
 	core/java/android/content/pm/IOnPermissionsChangeListener.aidl \
@@ -1409,6 +1410,8 @@
 # ====  c++ proto host library  ==============================
 include $(CLEAR_VARS)
 LOCAL_MODULE := libplatformprotos
+# b/34740546, work around clang-tidy segmentation fault.
+LOCAL_TIDY_CHECKS := -modernize*
 LOCAL_PROTOC_OPTIMIZE_TYPE := full
 LOCAL_PROTOC_FLAGS := \
     --include_source_info \
diff --git a/api/current.txt b/api/current.txt
index 56fedf6..add82c4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1119,6 +1119,7 @@
     field public static final int searchSuggestSelection = 16843224; // 0x10101d8
     field public static final int searchSuggestThreshold = 16843373; // 0x101026d
     field public static final int searchViewStyle = 16843904; // 0x1010480
+    field public static final int secondaryContentAlpha = 16843688; // 0x10103a8
     field public static final int secondaryProgress = 16843064; // 0x1010138
     field public static final int secondaryProgressTint = 16843879; // 0x1010467
     field public static final int secondaryProgressTintMode = 16843880; // 0x1010468
@@ -6911,6 +6912,7 @@
     method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
     method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
+    method public boolean isRequestPinAppWidgetSupported();
     method public void notifyAppWidgetViewDataChanged(int[], int);
     method public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
@@ -9790,6 +9792,7 @@
     field public int theme;
     field public int uiOptions;
     field public int uid;
+    field public java.lang.String volumeUuid;
   }
 
   public static class ApplicationInfo.DisplayNameComparator implements java.util.Comparator {
@@ -9918,7 +9921,8 @@
     method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
     method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
     method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
-    field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM";
+    field public static final java.lang.String ACTION_CONFIRM_PIN_APPWIDGET = "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
+    field public static final java.lang.String ACTION_CONFIRM_PIN_SHORTCUT = "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
     field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
   }
 
@@ -10860,6 +10864,7 @@
     method public int getDimensionPixelSize(int, int);
     method public android.graphics.drawable.Drawable getDrawable(int);
     method public float getFloat(int, float);
+    method public android.graphics.Typeface getFont(int);
     method public float getFraction(int, int, int, float);
     method public int getIndex(int);
     method public int getIndexCount();
@@ -15243,6 +15248,7 @@
     field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
     field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
     field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
+    field public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
   }
 
   public static abstract interface DisplayManager.DisplayListener {
@@ -21315,6 +21321,7 @@
     method public deprecated java.nio.ByteBuffer[] getInputBuffers();
     method public final android.media.MediaFormat getInputFormat();
     method public android.media.Image getInputImage(int);
+    method public android.os.Bundle getMetrics();
     method public final java.lang.String getName();
     method public java.nio.ByteBuffer getOutputBuffer(int);
     method public deprecated java.nio.ByteBuffer[] getOutputBuffers();
@@ -22140,6 +22147,7 @@
     method public int getCurrentPosition();
     method public android.media.BufferingParams getDefaultBufferingParams();
     method public int getDuration();
+    method public android.os.Bundle getMetrics();
     method public android.media.PlaybackParams getPlaybackParams();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
     method public android.media.SyncParams getSyncParams();
@@ -23851,8 +23859,16 @@
   }
 
   public static final class TvContract.Programs implements android.media.tv.TvContract.BaseTvColumns {
+    field public static final java.lang.String ASPECT_RATIO_16_9 = "ASPECT_RATIO_16_9";
+    field public static final java.lang.String ASPECT_RATIO_1_1 = "ASPECT_RATIO_1_1";
+    field public static final java.lang.String ASPECT_RATIO_2_3 = "ASPECT_RATIO_2_3";
+    field public static final java.lang.String ASPECT_RATIO_3_2 = "ASPECT_RATIO_3_2";
+    field public static final java.lang.String AVAILABILITY_AVAILABLE = "AVAILABILITY_AVAILABLE";
+    field public static final java.lang.String AVAILABILITY_FREE_WITH_SUBSCRIPTION = "AVAILABILITY_FREE_WITH_SUBSCRIPTION";
+    field public static final java.lang.String AVAILABILITY_PAID_CONTENT = "AVAILABILITY_PAID_CONTENT";
     field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language";
     field public static final java.lang.String COLUMN_AUTHOR = "author";
+    field public static final java.lang.String COLUMN_AVAILABILITY = "availability";
     field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
     field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre";
     field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id";
@@ -23869,7 +23885,12 @@
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+    field public static final java.lang.String COLUMN_ITEM_COUNT = "item_count";
+    field public static final java.lang.String COLUMN_LIVE = "live";
+    field public static final java.lang.String COLUMN_LOGO = "logo";
     field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final java.lang.String COLUMN_OFFER_PRICE = "offer_price";
+    field public static final java.lang.String COLUMN_POSTER_ART_ASPECT_RATIO = "poster_art_aspect_ratio";
     field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration";
     field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri";
@@ -23877,6 +23898,7 @@
     field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
     field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight";
     field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
+    field public static final java.lang.String COLUMN_RELEASE_DATE = "release_date";
     field public static final java.lang.String COLUMN_REVIEW_RATING = "review_rating";
     field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
@@ -23884,12 +23906,16 @@
     field public static final deprecated java.lang.String COLUMN_SEASON_NUMBER = "season_number";
     field public static final java.lang.String COLUMN_SEASON_TITLE = "season_title";
     field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
     field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+    field public static final java.lang.String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final java.lang.String COLUMN_TITLE = "title";
+    field public static final java.lang.String COLUMN_TYPE = "type";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
     field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height";
     field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width";
+    field public static final java.lang.String COLUMN_WATCH_NEXT_TYPE = "watch_next_type";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/program";
     field public static final android.net.Uri CONTENT_URI;
@@ -23902,7 +23928,22 @@
     field public static final java.lang.String INTERACTION_TYPE_VIEWS = "INTERACTION_TYPE_VIEWS";
     field public static final java.lang.String REVIEW_RATING_STYLE_PERCENTAGE = "REVIEW_RATING_STYLE_PERCENTAGE";
     field public static final java.lang.String REVIEW_RATING_STYLE_STARS = "REVIEW_RATING_STYLE_STARS";
-    field public static final java.lang.String REVIEW_RATING_STYLE_THUMPS_UP_DOWN = "REVIEW_RATING_STYLE_THUMPS_UP_DOWN";
+    field public static final java.lang.String REVIEW_RATING_STYLE_THUMBS_UP_DOWN = "REVIEW_RATING_STYLE_THUMBS_UP_DOWN";
+    field public static final java.lang.String TYPE_ALBUM = "TYPE_ALBUM";
+    field public static final java.lang.String TYPE_ARTIST = "TYPE_ARTIST";
+    field public static final java.lang.String TYPE_CHANNEL = "TYPE_CHANNEL";
+    field public static final java.lang.String TYPE_CLIP = "TYPE_CLIP";
+    field public static final java.lang.String TYPE_EVENT = "TYPE_EVENT";
+    field public static final java.lang.String TYPE_MOVIE = "TYPE_MOVIE";
+    field public static final java.lang.String TYPE_PLAYLIST = "TYPE_PLAYLIST";
+    field public static final java.lang.String TYPE_STATION = "TYPE_STATION";
+    field public static final java.lang.String TYPE_TRACK = "TYPE_TRACK";
+    field public static final java.lang.String TYPE_TV_EPISODE = "TYPE_TV_EPISODE";
+    field public static final java.lang.String TYPE_TV_SEASON = "TYPE_TV_SEASON";
+    field public static final java.lang.String TYPE_TV_SERIES = "TYPE_TV_SERIES";
+    field public static final java.lang.String WATCH_NEXT_TYPE_CONTINUE = "WATCH_NEXT_TYPE_CONTINUE";
+    field public static final java.lang.String WATCH_NEXT_TYPE_NEW = "WATCH_NEXT_TYPE_NEW";
+    field public static final java.lang.String WATCH_NEXT_TYPE_NEXT = "WATCH_NEXT_TYPE_NEXT";
   }
 
   public static final class TvContract.Programs.Genres {
@@ -25548,6 +25589,7 @@
     method public java.security.cert.X509Certificate getCaCertificate();
     method public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
     method public java.lang.String getDomainSuffixMatch();
     method public int getEapMethod();
     method public java.lang.String getIdentity();
@@ -25561,6 +25603,7 @@
     method public void setCaCertificate(java.security.cert.X509Certificate);
     method public void setCaCertificates(java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+    method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
     method public void setDomainSuffixMatch(java.lang.String);
     method public void setEapMethod(int);
     method public void setIdentity(java.lang.String);
@@ -35735,7 +35778,7 @@
     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);
+    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, 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
@@ -38816,6 +38859,7 @@
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method public void listen(android.telephony.PhoneStateListener, int);
+    method public boolean sendDialerCode(java.lang.String);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
@@ -42537,6 +42581,7 @@
     field public static final int FLAG_PRIVATE = 4; // 0x4
     field public static final int FLAG_ROUND = 16; // 0x10
     field public static final int FLAG_SECURE = 2; // 0x2
+    field public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
     field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
     field public static final int INVALID_DISPLAY = -1; // 0xffffffff
     field public static final int STATE_DOZE = 3; // 0x3
@@ -44449,8 +44494,8 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
-    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
-    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
diff --git a/api/system-current.txt b/api/system-current.txt
index b4a374f..a037eff 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1235,6 +1235,7 @@
     field public static final int searchSuggestSelection = 16843224; // 0x10101d8
     field public static final int searchSuggestThreshold = 16843373; // 0x101026d
     field public static final int searchViewStyle = 16843904; // 0x1010480
+    field public static final int secondaryContentAlpha = 16843688; // 0x10103a8
     field public static final int secondaryProgress = 16843064; // 0x1010138
     field public static final int secondaryProgressTint = 16843879; // 0x1010467
     field public static final int secondaryProgressTintMode = 16843880; // 0x1010468
@@ -7266,6 +7267,7 @@
     method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
     method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
+    method public boolean isRequestPinAppWidgetSupported();
     method public void notifyAppWidgetViewDataChanged(int[], int);
     method public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
@@ -10209,6 +10211,7 @@
     field public int theme;
     field public int uiOptions;
     field public int uid;
+    field public java.lang.String volumeUuid;
   }
 
   public static class ApplicationInfo.DisplayNameComparator implements java.util.Comparator {
@@ -10381,7 +10384,8 @@
     method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
     method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
     method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
-    field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM";
+    field public static final java.lang.String ACTION_CONFIRM_PIN_APPWIDGET = "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
+    field public static final java.lang.String ACTION_CONFIRM_PIN_SHORTCUT = "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
     field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
   }
 
@@ -11422,6 +11426,7 @@
     method public int getDimensionPixelSize(int, int);
     method public android.graphics.drawable.Drawable getDrawable(int);
     method public float getFloat(int, float);
+    method public android.graphics.Typeface getFont(int);
     method public float getFraction(int, int, int, float);
     method public int getIndex(int);
     method public int getIndexCount();
@@ -15820,6 +15825,7 @@
     field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
     field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
     field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
+    field public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
   }
 
   public static abstract interface DisplayManager.DisplayListener {
@@ -22919,6 +22925,7 @@
     method public deprecated java.nio.ByteBuffer[] getInputBuffers();
     method public final android.media.MediaFormat getInputFormat();
     method public android.media.Image getInputImage(int);
+    method public android.os.Bundle getMetrics();
     method public final java.lang.String getName();
     method public java.nio.ByteBuffer getOutputBuffer(int);
     method public deprecated java.nio.ByteBuffer[] getOutputBuffers();
@@ -23744,6 +23751,7 @@
     method public int getCurrentPosition();
     method public android.media.BufferingParams getDefaultBufferingParams();
     method public int getDuration();
+    method public android.os.Bundle getMetrics();
     method public android.media.PlaybackParams getPlaybackParams();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
     method public android.media.SyncParams getSyncParams();
@@ -25598,8 +25606,16 @@
   }
 
   public static final class TvContract.Programs implements android.media.tv.TvContract.BaseTvColumns {
+    field public static final java.lang.String ASPECT_RATIO_16_9 = "ASPECT_RATIO_16_9";
+    field public static final java.lang.String ASPECT_RATIO_1_1 = "ASPECT_RATIO_1_1";
+    field public static final java.lang.String ASPECT_RATIO_2_3 = "ASPECT_RATIO_2_3";
+    field public static final java.lang.String ASPECT_RATIO_3_2 = "ASPECT_RATIO_3_2";
+    field public static final java.lang.String AVAILABILITY_AVAILABLE = "AVAILABILITY_AVAILABLE";
+    field public static final java.lang.String AVAILABILITY_FREE_WITH_SUBSCRIPTION = "AVAILABILITY_FREE_WITH_SUBSCRIPTION";
+    field public static final java.lang.String AVAILABILITY_PAID_CONTENT = "AVAILABILITY_PAID_CONTENT";
     field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language";
     field public static final java.lang.String COLUMN_AUTHOR = "author";
+    field public static final java.lang.String COLUMN_AVAILABILITY = "availability";
     field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
     field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre";
     field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id";
@@ -25616,7 +25632,12 @@
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+    field public static final java.lang.String COLUMN_ITEM_COUNT = "item_count";
+    field public static final java.lang.String COLUMN_LIVE = "live";
+    field public static final java.lang.String COLUMN_LOGO = "logo";
     field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final java.lang.String COLUMN_OFFER_PRICE = "offer_price";
+    field public static final java.lang.String COLUMN_POSTER_ART_ASPECT_RATIO = "poster_art_aspect_ratio";
     field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration";
     field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri";
@@ -25624,6 +25645,7 @@
     field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
     field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight";
     field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
+    field public static final java.lang.String COLUMN_RELEASE_DATE = "release_date";
     field public static final java.lang.String COLUMN_REVIEW_RATING = "review_rating";
     field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
@@ -25631,13 +25653,17 @@
     field public static final deprecated java.lang.String COLUMN_SEASON_NUMBER = "season_number";
     field public static final java.lang.String COLUMN_SEASON_TITLE = "season_title";
     field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
     field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+    field public static final java.lang.String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final java.lang.String COLUMN_TITLE = "title";
     field public static final java.lang.String COLUMN_TRANSIENT = "transient";
+    field public static final java.lang.String COLUMN_TYPE = "type";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
     field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height";
     field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width";
+    field public static final java.lang.String COLUMN_WATCH_NEXT_TYPE = "watch_next_type";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/program";
     field public static final android.net.Uri CONTENT_URI;
@@ -25650,7 +25676,22 @@
     field public static final java.lang.String INTERACTION_TYPE_VIEWS = "INTERACTION_TYPE_VIEWS";
     field public static final java.lang.String REVIEW_RATING_STYLE_PERCENTAGE = "REVIEW_RATING_STYLE_PERCENTAGE";
     field public static final java.lang.String REVIEW_RATING_STYLE_STARS = "REVIEW_RATING_STYLE_STARS";
-    field public static final java.lang.String REVIEW_RATING_STYLE_THUMPS_UP_DOWN = "REVIEW_RATING_STYLE_THUMPS_UP_DOWN";
+    field public static final java.lang.String REVIEW_RATING_STYLE_THUMBS_UP_DOWN = "REVIEW_RATING_STYLE_THUMBS_UP_DOWN";
+    field public static final java.lang.String TYPE_ALBUM = "TYPE_ALBUM";
+    field public static final java.lang.String TYPE_ARTIST = "TYPE_ARTIST";
+    field public static final java.lang.String TYPE_CHANNEL = "TYPE_CHANNEL";
+    field public static final java.lang.String TYPE_CLIP = "TYPE_CLIP";
+    field public static final java.lang.String TYPE_EVENT = "TYPE_EVENT";
+    field public static final java.lang.String TYPE_MOVIE = "TYPE_MOVIE";
+    field public static final java.lang.String TYPE_PLAYLIST = "TYPE_PLAYLIST";
+    field public static final java.lang.String TYPE_STATION = "TYPE_STATION";
+    field public static final java.lang.String TYPE_TRACK = "TYPE_TRACK";
+    field public static final java.lang.String TYPE_TV_EPISODE = "TYPE_TV_EPISODE";
+    field public static final java.lang.String TYPE_TV_SEASON = "TYPE_TV_SEASON";
+    field public static final java.lang.String TYPE_TV_SERIES = "TYPE_TV_SERIES";
+    field public static final java.lang.String WATCH_NEXT_TYPE_CONTINUE = "WATCH_NEXT_TYPE_CONTINUE";
+    field public static final java.lang.String WATCH_NEXT_TYPE_NEW = "WATCH_NEXT_TYPE_NEW";
+    field public static final java.lang.String WATCH_NEXT_TYPE_NEXT = "WATCH_NEXT_TYPE_NEXT";
   }
 
   public static final class TvContract.Programs.Genres {
@@ -26109,6 +26150,7 @@
     ctor public LogMaker(int);
     ctor public LogMaker(java.lang.Object[]);
     method public android.metrics.LogMaker addTaggedData(int, java.lang.Object);
+    method public android.metrics.LogMaker clearTaggedData(int);
     method public void deserialize(java.lang.Object[]);
     method public int getCategory();
     method public long getCounterBucket();
@@ -26120,6 +26162,7 @@
     method public long getTimestamp();
     method public int getType();
     method public boolean isLongCounterBucket();
+    method public boolean isSubsetOf(android.metrics.LogMaker);
     method public boolean isValidValue(java.lang.Object);
     method public java.lang.Object[] serialize();
     method public android.metrics.LogMaker setCategory(int);
@@ -28057,6 +28100,7 @@
     method public java.security.cert.X509Certificate getCaCertificate();
     method public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
     method public java.lang.String getDomainSuffixMatch();
     method public int getEapMethod();
     method public java.lang.String getIdentity();
@@ -28070,6 +28114,7 @@
     method public void setCaCertificate(java.security.cert.X509Certificate);
     method public void setCaCertificates(java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+    method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
     method public void setDomainSuffixMatch(java.lang.String);
     method public void setEapMethod(int);
     method public void setIdentity(java.lang.String);
@@ -36610,6 +36655,7 @@
     field public static final java.lang.String ACTION_APPLICATION_DETAILS_SETTINGS = "android.settings.APPLICATION_DETAILS_SETTINGS";
     field public static final java.lang.String ACTION_APPLICATION_DEVELOPMENT_SETTINGS = "android.settings.APPLICATION_DEVELOPMENT_SETTINGS";
     field public static final java.lang.String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
+    field public static final java.lang.String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS";
     field public static final java.lang.String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
     field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
     field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
@@ -38759,7 +38805,7 @@
     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);
+    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, 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
@@ -42166,6 +42212,7 @@
     method public boolean isWorldPhone();
     method public void listen(android.telephony.PhoneStateListener, int);
     method public boolean needsOtaServiceProvisioning();
+    method public boolean sendDialerCode(java.lang.String);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
@@ -45936,6 +45983,7 @@
     field public static final int FLAG_PRIVATE = 4; // 0x4
     field public static final int FLAG_ROUND = 16; // 0x10
     field public static final int FLAG_SECURE = 2; // 0x2
+    field public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
     field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
     field public static final int INVALID_DISPLAY = -1; // 0xffffffff
     field public static final int STATE_DOZE = 3; // 0x3
@@ -47848,8 +47896,8 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
-    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
-    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
diff --git a/api/test-current.txt b/api/test-current.txt
index 9b2bf2b..99ff3a2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1119,6 +1119,7 @@
     field public static final int searchSuggestSelection = 16843224; // 0x10101d8
     field public static final int searchSuggestThreshold = 16843373; // 0x101026d
     field public static final int searchViewStyle = 16843904; // 0x1010480
+    field public static final int secondaryContentAlpha = 16843688; // 0x10103a8
     field public static final int secondaryProgress = 16843064; // 0x1010138
     field public static final int secondaryProgressTint = 16843879; // 0x1010467
     field public static final int secondaryProgressTintMode = 16843880; // 0x1010468
@@ -6934,6 +6935,7 @@
     method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
     method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
+    method public boolean isRequestPinAppWidgetSupported();
     method public void notifyAppWidgetViewDataChanged(int[], int);
     method public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
@@ -9818,6 +9820,7 @@
     field public int theme;
     field public int uiOptions;
     field public int uid;
+    field public java.lang.String volumeUuid;
   }
 
   public static class ApplicationInfo.DisplayNameComparator implements java.util.Comparator {
@@ -9947,7 +9950,8 @@
     method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
     method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
     method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
-    field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM";
+    field public static final java.lang.String ACTION_CONFIRM_PIN_APPWIDGET = "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
+    field public static final java.lang.String ACTION_CONFIRM_PIN_SHORTCUT = "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
     field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
   }
 
@@ -10211,6 +10215,7 @@
     method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public abstract boolean hasSystemFeature(java.lang.String);
     method public abstract boolean hasSystemFeature(java.lang.String, int);
+    method public abstract boolean isPermissionReviewModeEnabled();
     method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
     method public abstract boolean isSafeMode();
     method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -10893,6 +10898,7 @@
     method public int getDimensionPixelSize(int, int);
     method public android.graphics.drawable.Drawable getDrawable(int);
     method public float getFloat(int, float);
+    method public android.graphics.Typeface getFont(int);
     method public float getFraction(int, int, int, float);
     method public int getIndex(int);
     method public int getIndexCount();
@@ -15276,6 +15282,7 @@
     field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
     field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
     field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
+    field public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
   }
 
   public static abstract interface DisplayManager.DisplayListener {
@@ -21406,6 +21413,7 @@
     method public deprecated java.nio.ByteBuffer[] getInputBuffers();
     method public final android.media.MediaFormat getInputFormat();
     method public android.media.Image getInputImage(int);
+    method public android.os.Bundle getMetrics();
     method public final java.lang.String getName();
     method public java.nio.ByteBuffer getOutputBuffer(int);
     method public deprecated java.nio.ByteBuffer[] getOutputBuffers();
@@ -22231,6 +22239,7 @@
     method public int getCurrentPosition();
     method public android.media.BufferingParams getDefaultBufferingParams();
     method public int getDuration();
+    method public android.os.Bundle getMetrics();
     method public android.media.PlaybackParams getPlaybackParams();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
     method public android.media.SyncParams getSyncParams();
@@ -23942,8 +23951,16 @@
   }
 
   public static final class TvContract.Programs implements android.media.tv.TvContract.BaseTvColumns {
+    field public static final java.lang.String ASPECT_RATIO_16_9 = "ASPECT_RATIO_16_9";
+    field public static final java.lang.String ASPECT_RATIO_1_1 = "ASPECT_RATIO_1_1";
+    field public static final java.lang.String ASPECT_RATIO_2_3 = "ASPECT_RATIO_2_3";
+    field public static final java.lang.String ASPECT_RATIO_3_2 = "ASPECT_RATIO_3_2";
+    field public static final java.lang.String AVAILABILITY_AVAILABLE = "AVAILABILITY_AVAILABLE";
+    field public static final java.lang.String AVAILABILITY_FREE_WITH_SUBSCRIPTION = "AVAILABILITY_FREE_WITH_SUBSCRIPTION";
+    field public static final java.lang.String AVAILABILITY_PAID_CONTENT = "AVAILABILITY_PAID_CONTENT";
     field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language";
     field public static final java.lang.String COLUMN_AUTHOR = "author";
+    field public static final java.lang.String COLUMN_AVAILABILITY = "availability";
     field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
     field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre";
     field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id";
@@ -23960,7 +23977,12 @@
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+    field public static final java.lang.String COLUMN_ITEM_COUNT = "item_count";
+    field public static final java.lang.String COLUMN_LIVE = "live";
+    field public static final java.lang.String COLUMN_LOGO = "logo";
     field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final java.lang.String COLUMN_OFFER_PRICE = "offer_price";
+    field public static final java.lang.String COLUMN_POSTER_ART_ASPECT_RATIO = "poster_art_aspect_ratio";
     field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration";
     field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri";
@@ -23968,6 +23990,7 @@
     field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
     field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight";
     field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
+    field public static final java.lang.String COLUMN_RELEASE_DATE = "release_date";
     field public static final java.lang.String COLUMN_REVIEW_RATING = "review_rating";
     field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
@@ -23975,12 +23998,16 @@
     field public static final deprecated java.lang.String COLUMN_SEASON_NUMBER = "season_number";
     field public static final java.lang.String COLUMN_SEASON_TITLE = "season_title";
     field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
     field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+    field public static final java.lang.String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final java.lang.String COLUMN_TITLE = "title";
+    field public static final java.lang.String COLUMN_TYPE = "type";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
     field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height";
     field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width";
+    field public static final java.lang.String COLUMN_WATCH_NEXT_TYPE = "watch_next_type";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/program";
     field public static final android.net.Uri CONTENT_URI;
@@ -23993,7 +24020,22 @@
     field public static final java.lang.String INTERACTION_TYPE_VIEWS = "INTERACTION_TYPE_VIEWS";
     field public static final java.lang.String REVIEW_RATING_STYLE_PERCENTAGE = "REVIEW_RATING_STYLE_PERCENTAGE";
     field public static final java.lang.String REVIEW_RATING_STYLE_STARS = "REVIEW_RATING_STYLE_STARS";
-    field public static final java.lang.String REVIEW_RATING_STYLE_THUMPS_UP_DOWN = "REVIEW_RATING_STYLE_THUMPS_UP_DOWN";
+    field public static final java.lang.String REVIEW_RATING_STYLE_THUMBS_UP_DOWN = "REVIEW_RATING_STYLE_THUMBS_UP_DOWN";
+    field public static final java.lang.String TYPE_ALBUM = "TYPE_ALBUM";
+    field public static final java.lang.String TYPE_ARTIST = "TYPE_ARTIST";
+    field public static final java.lang.String TYPE_CHANNEL = "TYPE_CHANNEL";
+    field public static final java.lang.String TYPE_CLIP = "TYPE_CLIP";
+    field public static final java.lang.String TYPE_EVENT = "TYPE_EVENT";
+    field public static final java.lang.String TYPE_MOVIE = "TYPE_MOVIE";
+    field public static final java.lang.String TYPE_PLAYLIST = "TYPE_PLAYLIST";
+    field public static final java.lang.String TYPE_STATION = "TYPE_STATION";
+    field public static final java.lang.String TYPE_TRACK = "TYPE_TRACK";
+    field public static final java.lang.String TYPE_TV_EPISODE = "TYPE_TV_EPISODE";
+    field public static final java.lang.String TYPE_TV_SEASON = "TYPE_TV_SEASON";
+    field public static final java.lang.String TYPE_TV_SERIES = "TYPE_TV_SERIES";
+    field public static final java.lang.String WATCH_NEXT_TYPE_CONTINUE = "WATCH_NEXT_TYPE_CONTINUE";
+    field public static final java.lang.String WATCH_NEXT_TYPE_NEW = "WATCH_NEXT_TYPE_NEW";
+    field public static final java.lang.String WATCH_NEXT_TYPE_NEXT = "WATCH_NEXT_TYPE_NEXT";
   }
 
   public static final class TvContract.Programs.Genres {
@@ -25639,6 +25681,7 @@
     method public java.security.cert.X509Certificate getCaCertificate();
     method public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
     method public java.lang.String getDomainSuffixMatch();
     method public int getEapMethod();
     method public java.lang.String getIdentity();
@@ -25652,6 +25695,7 @@
     method public void setCaCertificate(java.security.cert.X509Certificate);
     method public void setCaCertificates(java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+    method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
     method public void setDomainSuffixMatch(java.lang.String);
     method public void setEapMethod(int);
     method public void setIdentity(java.lang.String);
@@ -35869,7 +35913,7 @@
     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);
+    method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, 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
@@ -38950,6 +38994,7 @@
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method public void listen(android.telephony.PhoneStateListener, int);
+    method public boolean sendDialerCode(java.lang.String);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
@@ -39752,6 +39797,7 @@
     method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public boolean hasSystemFeature(java.lang.String);
     method public boolean hasSystemFeature(java.lang.String, int);
+    method public boolean isPermissionReviewModeEnabled();
     method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
     method public boolean isSafeMode();
     method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -42839,6 +42885,7 @@
     field public static final int FLAG_PRIVATE = 4; // 0x4
     field public static final int FLAG_ROUND = 16; // 0x10
     field public static final int FLAG_SECURE = 2; // 0x2
+    field public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 32; // 0x20
     field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
     field public static final int INVALID_DISPLAY = -1; // 0xffffffff
     field public static final int STATE_DOZE = 3; // 0x3
@@ -44754,8 +44801,8 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
-    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 1; // 0x1
-    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 2; // 0x2
+    field public static final int AUTO_FILL_FLAG_TYPE_FILL = 268435456; // 0x10000000
+    field public static final int AUTO_FILL_FLAG_TYPE_SAVE = 536870912; // 0x20000000
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 634dc1fd..08ad976 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -469,8 +469,9 @@
      */
     @Override
     public boolean doAnimationFrame(long frameTime) {
-        // TODO: Need to find a better signal than this
-        return getDuration() + getStartDelay() >= frameTime;
+        // TODO: Need to find a better signal than this. There's a bug in SystemUI that's preventing
+        // returning !isStarted() from working.
+        return false;
     }
 
     /**
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index bca736a25..8aba405 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -174,6 +174,19 @@
      */
     private long mPauseTime = -1;
 
+    // This is to work around a bug in b/34736819. This needs to be removed once app team
+    // fixes their side.
+    private AnimatorListenerAdapter mDummyListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mNodeMap.get(animation) == null) {
+                throw new AndroidRuntimeException("Error: animation ended is not in the node map");
+            }
+            mNodeMap.get(animation).mEnded = true;
+
+        }
+    };
+
     public AnimatorSet() {
         super();
         mNodeMap.put(mDelayAnim, mRootNode);
@@ -1018,6 +1031,8 @@
     }
 
     private void startAnimation() {
+        addDummyListener();
+
         // Register animation callback
         addAnimationCallback(mStartDelay);
 
@@ -1062,6 +1077,20 @@
         }
     }
 
+    // This is to work around the issue in b/34736819, as the old behavior in AnimatorSet had
+    // masked a real bug in play movies. TODO: remove this and below once the root cause is fixed.
+    private void addDummyListener() {
+        for (int i = 1; i < mNodes.size(); i++) {
+            mNodes.get(i).mAnimation.addListener(mDummyListener);
+        }
+    }
+
+    private void removeDummyListener() {
+        for (int i = 1; i < mNodes.size(); i++) {
+            mNodes.get(i).mAnimation.removeListener(mDummyListener);
+        }
+    }
+
     private int findLatestEventIdForTime(long currentPlayTime) {
         int size = mEvents.size();
         int latestId = mLastEventId;
@@ -1107,6 +1136,7 @@
                 tmpListeners.get(i).onAnimationEnd(this, mReversing);
             }
         }
+        removeDummyListener();
         mSelfPulse = true;
         mReversing = false;
     }
@@ -1151,6 +1181,17 @@
         anim.mNodeMap = new ArrayMap<Animator, Node>();
         anim.mNodes = new ArrayList<Node>(nodeCount);
         anim.mEvents = new ArrayList<AnimationEvent>();
+        anim.mDummyListener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (anim.mNodeMap.get(animation) == null) {
+                    throw new AndroidRuntimeException("Error: animation ended is not in the node"
+                            + " map");
+                }
+                anim.mNodeMap.get(animation).mEnded = true;
+
+            }
+        };
         anim.mReversing = false;
         anim.mDependencyDirty = true;
 
diff --git a/core/java/android/annotation/FontRes.java b/core/java/android/annotation/FontRes.java
new file mode 100644
index 0000000..dbacb58
--- /dev/null
+++ b/core/java/android/annotation/FontRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a Font resource reference (e.g. R.font.myfont).
+ *
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface FontRes {
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 580bb50..1d84ff5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -117,9 +117,8 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.autofill.AutoFillId;
-import android.view.autofill.Dataset;
-import android.view.autofill.DatasetField;
-import android.view.autofill.VirtualViewDelegate;
+import android.view.autofill.AutoFillManager;
+import android.view.autofill.AutoFillSession;
 import android.widget.AdapterView;
 import android.widget.Toast;
 import android.widget.Toolbar;
@@ -848,10 +847,7 @@
     private boolean mHasCurrentPermissionsRequest;
 
     @GuardedBy("this")
-    private WeakReference<IAutoFillAppCallback> mAutoFillCallback;
-
-    @GuardedBy("this")
-    private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+    private AutoFillSession mAutoFillSession;
 
     private static native String getDlWarning();
 
@@ -1704,76 +1700,17 @@
     }
 
     /**
-     * Lazily sets the {@link #mAutoFillDelegateCallback}.
-     */
-    private void setAutoFillDelegateCallback() {
-        synchronized (this) {
-            if (mAutoFillDelegateCallback == null) {
-                mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
-                    // TODO(b/33197203): implement
-                };
-            }
-        }
-    }
-
-    /**
      * Lazily gets the {@link IAutoFillAppCallback} for this activitity.
      *
      * <p>This callback is used by the {@link AutoFillService} app to auto-fill the activity fields.
      */
-    WeakReference<IAutoFillAppCallback> getAutoFillCallback() {
+    IAutoFillAppCallback getAutoFillCallback() {
         synchronized (this) {
-            if (mAutoFillCallback == null) {
-                final IAutoFillAppCallback cb = new IAutoFillAppCallback.Stub() {
-                    @Override
-                    public void autoFill(Dataset dataset) throws RemoteException {
-                        // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
-                        // dataset.extras to service
-                        runOnUiThread(() -> {
-                            final View root = getWindow().getDecorView().getRootView();
-                            for (DatasetField field : dataset.getFields()) {
-                                final AutoFillId id = field.getId();
-                                if (id == null) {
-                                    Log.w(TAG, "autoFill(): null id on " + field);
-                                    continue;
-                                }
-                                final int viewId = id.getViewId();
-                                final View view = root.findViewByAccessibilityIdTraversal(viewId);
-                                if (view == null) {
-                                    Log.w(TAG, "autoFill(): no View with id " + viewId);
-                                    continue;
-                                }
-
-                                // TODO(b/33197203): handle protected value (like credit card)
-                                if (id.isVirtual()) {
-                                    // Delegate virtual fields to provider.
-                                    setAutoFillDelegateCallback();
-                                    final VirtualViewDelegate mgr = view
-                                            .getAutoFillVirtualViewDelegate(
-                                                    mAutoFillDelegateCallback);
-                                    if (mgr == null) {
-                                        Log.w(TAG, "autoFill(): cannot fill virtual " + id
-                                                + "; no auto-fill provider for view "
-                                                + view.getClass());
-                                        continue;
-                                    }
-                                    if (DEBUG_AUTO_FILL) {
-                                        Log.d(TAG, "autoFill(): delegating " + id
-                                                + " to virtual manager  " + mgr);
-                                    }
-                                    mgr.autoFill(id.getVirtualChildId(), field.getValue());
-                                } else {
-                                    // Handle non-virtual fields itself.
-                                    view.autoFill(field.getValue());
-                                }
-                            }
-                        });
-                    }
-                };
-                mAutoFillCallback = new WeakReference<IAutoFillAppCallback>(cb);
+            if (mAutoFillSession == null) {
+                mAutoFillSession = new AutoFillSession(this);
             }
+            return mAutoFillSession.getCallback();
         }
-        return mAutoFillCallback;
     }
 
     /**
@@ -6067,9 +6004,9 @@
             getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
         }
 
-        if (mAutoFillCallback != null) {
-            writer.print(prefix); writer.print("mAutoFillCallback: " );
-                    writer.println(mAutoFillCallback.get());
+        if (mAutoFillSession!= null) {
+            writer.print(prefix); writer.print("mAutoFillSession: " );
+                    writer.println(mAutoFillSession);
         }
 
         mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 89510d9..e848080 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -154,6 +154,12 @@
     public abstract List<IBinder> getTopVisibleActivities();
 
     /**
+     * Returns the top, focused activity of the currently visible stack, but only if it belongs to
+     * the given UID.
+     */
+    public abstract IBinder getTopVisibleActivity(int uid);
+
+    /**
      * Callback for window manager to let activity manager know that docked stack changes its
      * minimized state.
      */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 90fab41..cf20b68 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2967,7 +2967,7 @@
                 if (!forAutoFill) {
                     r.activity.onProvideAssistContent(content);
                 } else if (addAutoFillCallback) {
-                    IAutoFillAppCallback cb = r.activity.getAutoFillCallback().get();
+                    IAutoFillAppCallback cb = r.activity.getAutoFillCallback();
                     if (cb != null) {
                         data.putBinder(AutoFillService.KEY_CALLBACK, cb.asBinder());
                     } else {
@@ -5032,7 +5032,9 @@
      * @hide
      */
     public void stopProfiling() {
-        mProfiler.stopProfiling();
+        if (mProfiler != null) {
+            mProfiler.stopProfiling();
+        }
     }
 
     static final void handleDumpHeap(boolean managed, DumpHeapData dhd) {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 9f2fffd..2c2b279 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -310,6 +310,12 @@
     }
 
     @Override
+    public boolean isPermissionReviewModeEnabled() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_permissionReviewRequired);
+    }
+
+    @Override
     public PermissionGroupInfo getPermissionGroupInfo(String name,
             int flags) throws NameNotFoundException {
         try {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 73b96f1..612998d 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -762,6 +762,24 @@
      * are going to call back with {@link #onActivityResult(int, int, Intent)}.
      */
     public void setTargetFragment(Fragment fragment, int requestCode) {
+        // Don't allow a caller to set a target fragment in another FragmentManager,
+        // but there's a snag: people do set target fragments before fragments get added.
+        // We'll have the FragmentManager check that for validity when we move
+        // the fragments to a valid state.
+        final FragmentManager mine = getFragmentManager();
+        final FragmentManager theirs = fragment.getFragmentManager();
+        if (mine != null && theirs != null && mine != theirs) {
+            throw new IllegalArgumentException("Fragment " + fragment
+                    + " must share the same FragmentManager to be set as a target fragment");
+        }
+
+        // Don't let someone create a cycle.
+        for (Fragment check = fragment; check != null; check = check.getTargetFragment()) {
+            if (check == this) {
+                throw new IllegalArgumentException("Setting " + fragment + " as the target of "
+                        + this + " would create a target cycle");
+            }
+        }
         mTarget = fragment;
         mTargetRequestCode = requestCode;
     }
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 44f1322..32cf1c3 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1110,10 +1110,25 @@
                                 }
                             }
                         }
+
                         f.mHost = mHost;
                         f.mParentFragment = mParent;
                         f.mFragmentManager = mParent != null
                                 ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
+
+                        // If we have a target fragment, push it along to at least CREATED
+                        // so that this one can rely on it as an initialized dependency.
+                        if (f.mTarget != null) {
+                            if (!mActive.contains(f.mTarget)) {
+                                throw new IllegalStateException("Fragment " + f
+                                        + " declared target fragment " + f.mTarget
+                                        + " that does not belong to this FragmentManager!");
+                            }
+                            if (f.mTarget.mState < Fragment.CREATED) {
+                                moveToState(f.mTarget, Fragment.CREATED, 0, 0, true);
+                            }
+                        }
+
                         dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
                         f.mCalled = false;
                         f.onAttach(mHost.getContext());
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7bdf4cc..80f4985 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3498,7 +3498,9 @@
             if (p.title != null) {
                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
                 contentView.setTextViewText(R.id.title, p.title);
-                setTextViewColorPrimary(contentView, R.id.title);
+                if (!p.ambient) {
+                    setTextViewColorPrimary(contentView, R.id.title);
+                }
                 contentView.setViewLayoutWidth(R.id.title, showProgress
                         ? ViewGroup.LayoutParams.WRAP_CONTENT
                         : ViewGroup.LayoutParams.MATCH_PARENT);
@@ -3507,7 +3509,9 @@
                 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);
+                if (!p.ambient) {
+                    setTextViewColorSecondary(contentView, textId);
+                }
                 contentView.setViewVisibility(textId, View.VISIBLE);
             }
 
@@ -3715,7 +3719,7 @@
         }
         private void bindHeaderAppName(RemoteViews contentView, boolean ambient) {
             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
-            if (isColorized()) {
+            if (isColorized() && !ambient) {
                 setTextViewColorPrimary(contentView, R.id.app_name_text);
             } else {
                 contentView.setTextColor(R.id.app_name_text,
@@ -4067,7 +4071,7 @@
                 }
             } else {
                 button.setTextViewText(R.id.action0, processLegacyText(action.title));
-                if (isColorized()) {
+                if (isColorized() && !ambient) {
                     setTextViewColorPrimary(button, R.id.action0);
                 } else if (mN.color != COLOR_DEFAULT) {
                     button.setTextColor(R.id.action0,
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 3341b91..68cac27 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -64,7 +64,7 @@
             mId = null;
         }
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-        in.readList(mChannels, NotificationChannel.class.getClassLoader());
+        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
     }
 
     @Override
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 1f4fe67..5205959 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1131,7 +1131,7 @@
      * can be used from within background operations like broadcast receivers
      * or scheduled jobs.
      *
-     * @param service Description of the service to be stopped.  The Intent must be either
+     * @param service Description of the service to be started.  The Intent must be either
      *      fully explicit (supplying a component name) or specify a specific package
      *      name it is targeted to.
      * @param id The identifier for this notification as per
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5b78ce8..4be011e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -34,6 +34,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
@@ -909,6 +910,16 @@
      * or the organization name.
      *
      * <p> Use in Bundle {@link #EXTRA_PROVISIONING_DISCLAIMERS}
+     *
+     * <p> System app, i.e. application with {@link ApplicationInfo#FLAG_SYSTEM}, can also insert a
+     * disclaimer by declaring an application-level meta-data in {@code AndroidManifest.xml}.
+     * Must use it with {@link #EXTRA_PROVISIONING_DISCLAIMER_CONTENT}. Here is the example:
+     *
+     * <pre>
+     *  &lt;meta-data
+     *      android:name="android.app.extra.PROVISIONING_DISCLAIMER_HEADER"
+     *      android:resource="@string/disclaimer_header"
+     * /&gt;</pre>
      */
     public static final String EXTRA_PROVISIONING_DISCLAIMER_HEADER =
             "android.app.extra.PROVISIONING_DISCLAIMER_HEADER";
@@ -931,6 +942,16 @@
      * {@link android.content.ClipData} of the intent too.
      *
      * <p> Use in Bundle {@link #EXTRA_PROVISIONING_DISCLAIMERS}
+     *
+     * <p> System app, i.e. application with {@link ApplicationInfo#FLAG_SYSTEM}, can also insert a
+     * disclaimer by declaring an application-level meta-data in {@code AndroidManifest.xml}.
+     * Must use it with {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER}. Here is the example:
+     *
+     * <pre>
+     *  &lt;meta-data
+     *      android:name="android.app.extra.PROVISIONING_DISCLAIMER_CONTENT"
+     *      android:resource="@string/disclaimer_content"
+     * /&gt;</pre>
      */
     public static final String EXTRA_PROVISIONING_DISCLAIMER_CONTENT =
             "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT";
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
index 695994b..9d30771 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.WorkerThread;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.os.RemoteException;
 import android.os.UserHandle;
 
@@ -99,6 +100,8 @@
      * @param volumeUuid the UUID of the storage volume you're interested in, or
      *            {@code null} to specify the default internal storage.
      * @param uid the UID you're interested in.
+     * @see ApplicationInfo#volumeUuid
+     * @see ApplicationInfo#uid
      */
     @WorkerThread
     public StorageStats queryStatsForUid(String volumeUuid, int uid) {
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 9980e966..077331e 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1072,6 +1072,18 @@
     }
 
     /**
+     * Return {@code TRUE} if the default launcher supports
+     * {@link #requestPinAppWidget(ComponentName, PendingIntent)}
+     */
+    public boolean isRequestPinAppWidgetSupported() {
+        try {
+            return mService.isRequestPinAppWidgetSupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Request to pin an app widget on the current launcher. It's up to the launcher to accept this
      * request (optionally showing a user confirmation). If the request is accepted, the caller will
      * get a confirmation with extra {@link #EXTRA_APPWIDGET_ID}.
@@ -1095,6 +1107,7 @@
      *
      * @see android.content.pm.ShortcutManager#isRequestPinShortcutSupported()
      * @see android.content.pm.ShortcutManager#requestPinShortcut(ShortcutInfo, IntentSender)
+     * @see #isRequestPinAppWidgetSupported()
      *
      * @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
      * service or when the user is locked.
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 52cd2de..a37a0b3 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -51,9 +51,10 @@
     // NOTE: The values should be same as those listed in the following file:
     //   hardware/libhardware/include/hardware/bt_av.h
     public static final int SOURCE_CODEC_TYPE_SBC     = 0;
-    public static final int SOURCE_CODEC_TYPE_APTX    = 1;
-    public static final int SOURCE_CODEC_TYPE_APTX_HD = 2;
-    public static final int SOURCE_CODEC_TYPE_LDAC    = 3;
+    public static final int SOURCE_CODEC_TYPE_AAC     = 1;
+    public static final int SOURCE_CODEC_TYPE_APTX    = 2;
+    public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
+    public static final int SOURCE_CODEC_TYPE_LDAC    = 4;
 
     public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c7c680f..f610a29 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3754,6 +3754,16 @@
     public static final String INCIDENT_SERVICE = "incident";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.content.om.OverlayManager} for managing overlay packages.
+     *
+     * @see #getSystemService
+     * @see android.content.om.OverlayManager
+     * @hide
+     */
+    public static final String OVERLAY_SERVICE = "overlay";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 249001b..111b4d6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3133,6 +3133,40 @@
             "android.intent.action.MEDIA_RESOURCE_GRANTED";
 
     /**
+     * Broadcast Action: An overlay package has been installed. The data
+     * contains the name of the added overlay package.
+     * @hide
+     */
+    public static final String ACTION_OVERLAY_ADDED = "android.intent.action.OVERLAY_ADDED";
+
+    /**
+     * Broadcast Action: An overlay package has changed. The data contains the
+     * name of the overlay package which has changed. This is broadcast on all
+     * changes to the OverlayInfo returned by {@link
+     * android.content.om.IOverlayManager#getOverlayInfo(String, int)}. The
+     * most common change is a state change that will change whether the
+     * overlay is enabled or not.
+     * @hide
+     */
+    public static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+
+    /**
+     * Broadcast Action: An overlay package has been removed. The data contains
+     * the name of the overlay package which has been removed.
+     * @hide
+     */
+    public static final String ACTION_OVERLAY_REMOVED = "android.intent.action.OVERLAY_REMOVED";
+
+    /**
+     * Broadcast Action: The order of a package's list of overlay packages has
+     * changed. The data contains the package name of the overlay package that
+     * had its position in the list adjusted.
+     * @hide
+     */
+    public static final String
+            ACTION_OVERLAY_PRIORITY_CHANGED = "android.intent.action.OVERLAY_PRIORITY_CHANGED";
+
+    /**
      * Activity Action: Allow the user to select and return one or more existing
      * documents. When invoked, the system will display the various
      * {@link DocumentsProvider} instances installed on the device, letting the
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
new file mode 100644
index 0000000..4f5d960
--- /dev/null
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -0,0 +1,129 @@
+/*
+ * 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 android.content.om;
+
+import android.content.om.OverlayInfo;
+
+/**
+ * Api for getting information about overlay packages.
+ *
+ * {@hide}
+ */
+interface IOverlayManager {
+    /**
+     * Returns information about all installed overlay packages for the
+     * specified user. If there are no installed overlay packages for this user,
+     * an empty map is returned (i.e. null is never returned). The returned map is a
+     * mapping of target package names to lists of overlays. Each list for a
+     * given target package is sorted in priority order, with the overlay with
+     * the highest priority at the end of the list.
+     *
+     * @param userId The user to get the OverlayInfos for.
+     * @return A Map<String, List<OverlayInfo>> with target package names
+     *         mapped to lists of overlays; if no overlays exist for the
+     *         requested user, an empty map is returned.
+     */
+    Map getAllOverlays(in int userId);
+
+    /**
+     * Returns information about all overlays for the given target package for
+     * the specified user. The returned list is ordered according to the
+     * overlay priority with the highest priority at the end of the list.
+     *
+     * @param targetPackageName The name of the target package.
+     * @param userId The user to get the OverlayInfos for.
+     * @return A list of OverlayInfo objects; if no overlays exist for the
+     *         requested package, an empty list is returned.
+     */
+    List getOverlayInfosForTarget(in String targetPackageName, in int userId);
+
+    /**
+     * Returns information about the overlay with the given package name for the
+     * specified user.
+     *
+     * @param packageName The name of the overlay package.
+     * @param userId The user to get the OverlayInfo for.
+     * @return The OverlayInfo for the overlay package; or null if no such
+     *         overlay package exists.
+     */
+    OverlayInfo getOverlayInfo(in String packageName, in int userId);
+
+    /**
+     * Request that an overlay package be enabled or disabled when possible to
+     * do so.
+     *
+     * It is always possible to disable an overlay, but due to technical and
+     * security reasons it may not always be possible to enable an overlay. An
+     * example of the latter is when the related target package is not
+     * installed. If the technical obstacle is later overcome, the overlay is
+     * automatically enabled at that point in time.
+     *
+     * An enabled overlay is a part of target package's resources, i.e. it will
+     * be part of any lookups performed via {@link android.content.res.Resources}
+     * and {@link android.content.res.AssetManager}. A disabled overlay will no
+     * longer affect the resources of the target package. If the target is
+     * currently running, its outdated resources will be replaced by new ones.
+     * This happens the same way as when an application enters or exits split
+     * window mode.
+     *
+     * @param packageName The name of the overlay package.
+     * @param enable true to enable the overlay, false to disable it.
+     * @param userId The user for which to change the overlay.
+     * @return true if the system successfully registered the request, false
+     *         otherwise.
+     */
+    boolean setEnabled(in String packageName, in boolean enable, in int userId);
+
+    /**
+     * Change the priority of the given overlay to be just higher than the
+     * overlay with package name newParentPackageName. Both overlay packages
+     * must have the same target and user.
+     *
+     * @see getOverlayInfosForTarget
+     *
+     * @param packageName The name of the overlay package whose priority should
+     *        be adjusted.
+     * @param newParentPackageName The name of the overlay package the newly
+     *        adjusted overlay package should just outrank.
+     * @param userId The user for which to change the overlay.
+     */
+    boolean setPriority(in String packageName, in String newParentPackageName, in int userId);
+
+    /**
+     * Change the priority of the given overlay to the highest priority relative to
+     * the other overlays with the same target and user.
+     *
+     * @see getOverlayInfosForTarget
+     *
+     * @param packageName The name of the overlay package whose priority should
+     *        be adjusted.
+     * @param userId The user for which to change the overlay.
+     */
+    boolean setHighestPriority(in String packageName, in int userId);
+
+    /**
+     * Change the priority of the overlay to the lowest priority relative to
+     * the other overlays for the same target and user.
+     *
+     * @see getOverlayInfosForTarget
+     *
+     * @param packageName The name of the overlay package whose priority should
+     *        be adjusted.
+     * @param userId The user for which to change the overlay.
+     */
+    boolean setLowestPriority(in String packageName, in int userId);
+}
diff --git a/core/java/android/content/om/OverlayInfo.aidl b/core/java/android/content/om/OverlayInfo.aidl
new file mode 100644
index 0000000..e7d413d
--- /dev/null
+++ b/core/java/android/content/om/OverlayInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.content.om;
+
+parcelable OverlayInfo;
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
new file mode 100644
index 0000000..1a207ba
--- /dev/null
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -0,0 +1,263 @@
+/*
+ * 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 android.content.om;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Immutable overlay information about a package. All PackageInfos that
+ * represent an overlay package will have a corresponding OverlayInfo.
+ *
+ * @hide
+ */
+public final class OverlayInfo implements Parcelable {
+    /**
+     * An internal state used as the initial state of an overlay. OverlayInfo
+     * objects exposed outside the {@link
+     * com.android.server.om.OverlayManagerService} should never have this
+     * state.
+     */
+    public static final int STATE_UNKNOWN = -1;
+
+    /**
+     * The target package of the overlay is not installed. The overlay cannot be enabled.
+     */
+    public static final int STATE_MISSING_TARGET = 0;
+
+    /**
+     * Creation of idmap file failed (e.g. no matching resources). The overlay
+     * cannot be enabled.
+     */
+    public static final int STATE_NO_IDMAP = 1;
+
+    /**
+     * The overlay is currently disabled. It can be enabled.
+     *
+     * @see IOverlayManager.setEnabled
+     */
+    public static final int STATE_DISABLED = 2;
+
+    /**
+     * The overlay is currently enabled. It can be disabled.
+     *
+     * @see IOverlayManager.setEnabled
+     */
+    public static final int STATE_ENABLED = 3;
+
+    /**
+     * Package name of the overlay package
+     */
+    public final String packageName;
+
+    /**
+     * Package name of the target package
+     */
+    public final String targetPackageName;
+
+    /**
+     * Full path to the base APK for this overlay package
+     */
+    public final String baseCodePath;
+
+    /**
+     * The state of this OverlayInfo as defined by the STATE_* constants in this class.
+     *
+     * @see #STATE_MISSING_TARGET
+     * @see #STATE_NO_IDMAP
+     * @see #STATE_DISABLED
+     * @see #STATE_ENABLED
+     */
+    public final int state;
+
+    /**
+     * User handle for which this overlay applies
+     */
+    public final int userId;
+
+    /**
+     * Create a new OverlayInfo based on source with an updated state.
+     *
+     * @param source the source OverlayInfo to base the new instance on
+     * @param state the new state for the source OverlayInfo
+     */
+    public OverlayInfo(@NonNull OverlayInfo source, int state) {
+        this(source.packageName, source.targetPackageName, source.baseCodePath, state,
+                source.userId);
+    }
+
+    public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
+            @NonNull String baseCodePath, int state, int userId) {
+        this.packageName = packageName;
+        this.targetPackageName = targetPackageName;
+        this.baseCodePath = baseCodePath;
+        this.state = state;
+        this.userId = userId;
+        ensureValidState();
+    }
+
+    public OverlayInfo(Parcel source) {
+        packageName = source.readString();
+        targetPackageName = source.readString();
+        baseCodePath = source.readString();
+        state = source.readInt();
+        userId = source.readInt();
+        ensureValidState();
+    }
+
+    private void ensureValidState() {
+        if (packageName == null) {
+            throw new IllegalArgumentException("packageName must not be null");
+        }
+        if (targetPackageName == null) {
+            throw new IllegalArgumentException("targetPackageName must not be null");
+        }
+        if (baseCodePath == null) {
+            throw new IllegalArgumentException("baseCodePath must not be null");
+        }
+        switch (state) {
+            case STATE_UNKNOWN:
+            case STATE_MISSING_TARGET:
+            case STATE_NO_IDMAP:
+            case STATE_DISABLED:
+            case STATE_ENABLED:
+                break;
+            default:
+                throw new IllegalArgumentException("State " + state + " is not a valid state");
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(packageName);
+        dest.writeString(targetPackageName);
+        dest.writeString(baseCodePath);
+        dest.writeInt(state);
+        dest.writeInt(userId);
+    }
+
+    public static final Parcelable.Creator<OverlayInfo> CREATOR = new Parcelable.Creator<OverlayInfo>() {
+        @Override
+        public OverlayInfo createFromParcel(Parcel source) {
+            return new OverlayInfo(source);
+        }
+
+        @Override
+        public OverlayInfo[] newArray(int size) {
+            return new OverlayInfo[size];
+        }
+    };
+
+    /**
+     * Return true if this overlay is enabled, i.e. should be used to overlay
+     * the resources in the target package.
+     *
+     * Disabled overlay packages are installed but are currently not in use.
+     *
+     * @return true if the overlay is enabled, else false.
+     */
+    public boolean isEnabled() {
+        switch (state) {
+            case STATE_ENABLED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Translate a state to a human readable string. Only intended for
+     * debugging purposes.
+     *
+     * @see #STATE_MISSING_TARGET
+     * @see #STATE_NO_IDMAP
+     * @see #STATE_DISABLED
+     * @see #STATE_ENABLED
+     *
+     * @return a human readable String representing the state.
+     */
+    public static String stateToString(int state) {
+        switch (state) {
+            case STATE_UNKNOWN:
+                return "STATE_UNKNOWN";
+            case STATE_MISSING_TARGET:
+                return "STATE_MISSING_TARGET";
+            case STATE_NO_IDMAP:
+                return "STATE_NO_IDMAP";
+            case STATE_DISABLED:
+                return "STATE_DISABLED";
+            case STATE_ENABLED:
+                return "STATE_ENABLED";
+            default:
+                return "<unknown state>";
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + userId;
+        result = prime * result + state;
+        result = prime * result + ((packageName == null) ? 0 : packageName.hashCode());
+        result = prime * result + ((targetPackageName == null) ? 0 : targetPackageName.hashCode());
+        result = prime * result + ((baseCodePath == null) ? 0 : baseCodePath.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        OverlayInfo other = (OverlayInfo) obj;
+        if (userId != other.userId) {
+            return false;
+        }
+        if (state != other.state) {
+            return false;
+        }
+        if (!packageName.equals(other.packageName)) {
+            return false;
+        }
+        if (!targetPackageName.equals(other.targetPackageName)) {
+            return false;
+        }
+        if (!baseCodePath.equals(other.baseCodePath)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "OverlayInfo { overlay=" + packageName + ", target=" + targetPackageName + ", state="
+                + state + " (" + stateToString(state) + "), userId=" + userId + " }";
+    }
+}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 3d9ba96..ec74617 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -596,8 +596,13 @@
      */
     public int largestWidthLimitDp = 0;
 
-    /** {@hide} */
+    /**
+     * UUID of the storage volume on which this application is being hosted. For
+     * apps hosted on the default internal storage at
+     * {@link Environment#getDataDirectory()}, the UUID value is {@code null}.
+     */
     public String volumeUuid;
+
     /** {@hide} */
     public String scanSourceDir;
     /** {@hide} */
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index c90134a..03124be 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -71,5 +71,5 @@
 
     void applyRestore(in byte[] payload, int user);
 
-    boolean isRequestPinShortcutSupported(int user);
+    boolean isRequestPinItemSupported(int user, int requestType);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 57d2ba7..999b34f 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -93,22 +93,40 @@
      * @see #EXTRA_PIN_ITEM_REQUEST
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_CONFIRM_PIN_ITEM =
-            "android.content.pm.action.CONFIRM_PIN_ITEM";
+    public static final String ACTION_CONFIRM_PIN_SHORTCUT =
+            "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
 
     /**
-     * An extra for {@link #ACTION_CONFIRM_PIN_ITEM} containing a
-     * {@link ShortcutInfo} of the shortcut the publisher app asked to pin.
+     * Activity Action: For the default launcher to show the confirmation dialog to create
+     * a pinned app widget.
+     *
+     * <p>See the {@link android.appwidget.AppWidgetManager#requestPinAppWidget} javadoc for
+     * details.
+     *
+     * <p>
+     * Use {@link #getPinItemRequest(Intent)} to get a {@link PinItemRequest} object,
+     * and call {@link PinItemRequest#accept(Bundle)}
+     * if the user accepts.  If the user doesn't accept, no further action is required.
+     *
+     * @see #EXTRA_PIN_ITEM_REQUEST
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CONFIRM_PIN_APPWIDGET =
+            "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
+
+    /**
+     * An extra for {@link #ACTION_CONFIRM_PIN_SHORTCUT} &amp; {@link #ACTION_CONFIRM_PIN_APPWIDGET}
+     * containing a {@link PinItemRequest} of appropriate type asked to pin.
      *
      * <p>A helper function {@link #getPinItemRequest(Intent)} can be used
      * instead of using this constant directly.
      *
-     * @see #ACTION_CONFIRM_PIN_ITEM
+     * @see #ACTION_CONFIRM_PIN_SHORTCUT
+     * @see #ACTION_CONFIRM_PIN_APPWIDGET
      */
     public static final String EXTRA_PIN_ITEM_REQUEST =
             "android.content.pm.extra.PIN_ITEM_REQUEST";
 
-
     private Context mContext;
     private ILauncherApps mService;
     private PackageManager mPm;
@@ -1208,8 +1226,9 @@
     }
 
     /**
-     * Represents a "pin shortcut" request made by an app, which is sent with
-     * an {@link #ACTION_CONFIRM_PIN_ITEM} intent to the default launcher app.
+     * Represents a "pin shortcut" or a "pin appwidget" request made by an app, which is sent with
+     * an {@link #ACTION_CONFIRM_PIN_SHORTCUT} or {@link #ACTION_CONFIRM_PIN_APPWIDGET} intent
+     * respectively to the default launcher app.
      *
      * <p>Note the launcher may receive a request to pin a shortcut that is already pinned, because
      * the user may actually want to have multiple icons of the same shortcut on the launcher.
@@ -1218,6 +1237,9 @@
      * even if the launcher does not call it, the shortcut is already pinned.  Also in this case,
      * the {@code options} argument to {@link #accept(Bundle)} will be ignored.
      *
+     * <p>For AppWidget pin requests launcher should send back the appwidget id as an extra for
+     * {@link #accept(Bundle)} as {@link android.appwidget.AppWidgetManager#EXTRA_APPWIDGET_ID}.
+     *
      * @see #EXTRA_PIN_ITEM_REQUEST
      * @see #getPinItemRequest(Intent)
      */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 184f1856..76c69ca 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2969,6 +2969,14 @@
             @PermissionInfoFlags int flags) throws NameNotFoundException;
 
     /**
+     * Returns true if Permission Review Mode is enabled, false otherwise.
+     *
+     * @hide
+     */
+    @TestApi
+    public abstract boolean isPermissionReviewModeEnabled();
+
+    /**
      * Retrieve all of the information we know about a particular group of
      * permissions.
      *
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index a90b18a..e3e02b1f 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -267,4 +267,33 @@
     }
 
     public abstract void setExternalSourcesPolicy(ExternalSourcesPolicy policy);
+
+    /**
+     * Get all overlay packages for a user.
+     * @param userId The user for which to get the overlays.
+     * @return A list of overlay packages. An empty list is returned if the
+     *         user has no installed overlay packages.
+     */
+    public abstract List<PackageInfo> getOverlayPackages(int userId);
+
+    /**
+     * Get the names of all target packages for a user.
+     * @param userId The user for which to get the package names.
+     * @return A list of target package names. This list includes the "android" package.
+     */
+    public abstract List<String> getTargetPackageNames(int userId);
+
+    /**
+     * Set which overlay to use for a package.
+     * @param userId The user for which to update the overlays.
+     * @param targetPackageName The package name of the package for which to update the overlays.
+     * @param overlayPackageNames The complete list of overlay packages that should be enabled for
+     *                            the target. Previously enabled overlays not specified in the list
+     *                            will be disabled. Pass in null or an empty list to disable
+     *                            all overlays. The order of the items is significant if several
+     *                            overlays modify the same resource.
+     * @return true if all packages names were known by the package manager, false otherwise
+     */
+    public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName,
+            List<String> overlayPackageNames);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8223726..7032cc0 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1232,7 +1232,7 @@
         XmlResourceParser parser = null;
         try {
             res = new Resources(assets, mMetrics, null);
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     Build.VERSION.RESOURCES_SDK_INT);
             parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
 
@@ -1568,7 +1568,7 @@
 
     private static AssetManager newConfiguredAssetManager() {
         AssetManager assetManager = new AssetManager();
-        assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                 Build.VERSION.RESOURCES_SDK_INT);
         return assetManager;
     }
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 805054f..fb280a1 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -830,7 +830,8 @@
      */
     public boolean isRequestPinShortcutSupported() {
         try {
-            return mService.isRequestPinShortcutSupported(injectMyUserId());
+            return mService.isRequestPinItemSupported(injectMyUserId(),
+                    LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 4773c73..87a6d4a 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -74,4 +74,6 @@
     public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
             @NonNull AppWidgetProviderInfo appWidget, @Nullable IntentSender resultIntent,
             int userId);
+
+    public abstract boolean isRequestPinItemSupported(int callingUserId, int requestType);
 }
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
index 5a9966d..99eb470 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -65,7 +65,7 @@
 
         AssetManager assets = new AssetManager();
         try {
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     Build.VERSION.RESOURCES_SDK_INT);
             loadApkIntoAssetManager(assets, mBaseCodePath, mFlags);
 
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 3ad45b6..4df90eb 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -83,7 +83,7 @@
             throws PackageParser.PackageParserException {
         final AssetManager assets = new AssetManager();
         try {
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     Build.VERSION.RESOURCES_SDK_INT);
 
             for (String assetPath : assetPaths) {
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index db24ffe..37e32ff 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration.NativeConfig;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
@@ -183,6 +184,11 @@
             if (block < 0) {
                 return null;
             }
+
+            // Convert the changing configurations flags populated by native code.
+            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+                    outValue.changingConfigurations);
+
             if (outValue.type == TypedValue.TYPE_STRING) {
                 return mStringBlocks[block].get(outValue.data);
             }
@@ -220,6 +226,11 @@
         if (block < 0) {
             return false;
         }
+
+        // Convert the changing configurations flags populated by native code.
+        outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+                outValue.changingConfigurations);
+
         if (outValue.type == TypedValue.TYPE_STRING) {
             outValue.string = mStringBlocks[block].get(outValue.data);
         }
@@ -266,6 +277,11 @@
         if (block < 0) {
             return false;
         }
+
+        // Convert the changing configurations flags populated by native code.
+        outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+                outValue.changingConfigurations);
+
         if (outValue.type == TypedValue.TYPE_STRING) {
             final StringBlock[] blocks = ensureStringBlocks();
             outValue.string = blocks[block].get(outValue.data);
@@ -759,7 +775,7 @@
             int orientation, int touchscreen, int density, int keyboard,
             int keyboardHidden, int navigation, int screenWidth, int screenHeight,
             int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
-            int screenLayout, int uiMode, int majorVersion);
+            int screenLayout, int uiMode, int colorMode, int majorVersion);
 
     /**
      * Retrieve the resource identifier for the given resource name.
diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java
new file mode 100644
index 0000000..15f3a09
--- /dev/null
+++ b/core/java/android/content/res/FontResourcesParser.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.res;
+
+import com.android.internal.R;
+import android.text.FontConfig;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parser for xml type font resources.
+ * @hide
+ */
+public class FontResourcesParser {
+    private static final int NORMAL_WEIGHT = 400;
+    private static final String ITALIC = "italic";
+
+    public static FontConfig parse(XmlPullParser parser, Resources resources)
+            throws XmlPullParserException, IOException {
+        int type;
+        while ((type=parser.next()) != XmlPullParser.START_TAG
+                && type != XmlPullParser.END_DOCUMENT) {
+            // Empty loop.
+        }
+
+        if (type != XmlPullParser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+        return readFamilies(parser, resources);
+    }
+
+    private static FontConfig readFamilies(XmlPullParser parser, Resources resources)
+            throws XmlPullParserException, IOException {
+        FontConfig config = new FontConfig();
+        parser.require(XmlPullParser.START_TAG, null, "font-family");
+        String tag = parser.getName();
+        if (tag.equals("font-family")) {
+            config.getFamilies().add(readFamily(parser, resources));
+        } else {
+            skip(parser);
+        }
+        return config;
+    }
+
+    private static FontConfig.Family readFamily(XmlPullParser parser, Resources resources)
+            throws XmlPullParserException, IOException {
+        List<FontConfig.Font> fonts = new ArrayList<>();
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            String tag = parser.getName();
+            if (tag.equals("font")) {
+                fonts.add(readFont(parser, resources));
+            } else {
+                skip(parser);
+            }
+        }
+        return new FontConfig.Family(null, fonts, null, null);
+    }
+
+    private static FontConfig.Font readFont(XmlPullParser parser, Resources resources)
+            throws XmlPullParserException, IOException {
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+        TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont);
+        int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight, NORMAL_WEIGHT);
+        boolean isItalic = ITALIC.equals(array.getString(R.styleable.FontFamilyFont_fontStyle));
+        String filename = array.getString(R.styleable.FontFamilyFont_font);
+        array.recycle();
+        return new FontConfig.Font(filename, 0, null, weight, isItalic);
+    }
+
+    private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+        int depth = 1;
+        while (depth > 0) {
+            switch (parser.next()) {
+                case XmlPullParser.START_TAG:
+                    depth++;
+                    break;
+                case XmlPullParser.END_TAG:
+                    depth--;
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index c3185a7..04e4454 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -27,6 +27,7 @@
 import android.annotation.ColorRes;
 import android.annotation.DimenRes;
 import android.annotation.DrawableRes;
+import android.annotation.FontRes;
 import android.annotation.FractionRes;
 import android.annotation.IntegerRes;
 import android.annotation.LayoutRes;
@@ -39,6 +40,7 @@
 import android.annotation.StyleableRes;
 import android.annotation.XmlRes;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.Config;
 import android.graphics.Movie;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
@@ -349,12 +351,12 @@
      *
      * @return Typeface The Typeface data associated with the resource.
      */
-    @NonNull public Typeface getFont(@StringRes int id) throws NotFoundException {
+    @NonNull public Typeface getFont(@FontRes int id) throws NotFoundException {
         final TypedValue value = obtainTempTypedValue();
         try {
             final ResourcesImpl impl = mResourcesImpl;
             impl.getValue(id, value, true);
-            Typeface typeface = impl.loadFont(value, id);
+            Typeface typeface = impl.loadFont(this, value, id);
             if (typeface != null) {
                 return typeface;
             }
@@ -365,6 +367,11 @@
                 + Integer.toHexString(id));
     }
 
+    @NonNull
+    Typeface getFont(@NonNull TypedValue value, @FontRes int id) throws NotFoundException {
+        return mResourcesImpl.loadFont(this, value, id);
+    }
+
     /**
      * Returns the character sequence necessary for grammatically correct pluralization
      * of the given resource ID for the given quantity.
@@ -1604,7 +1611,7 @@
          *         {@link ActivityInfo}
          * @see ActivityInfo
          */
-        public int getChangingConfigurations() {
+        public @Config int getChangingConfigurations() {
             return mThemeImpl.getChangingConfigurations();
         }
 
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 05892e0..3cf36d7 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -30,6 +30,7 @@
 import android.annotation.StyleableRes;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
+import android.content.res.Configuration.NativeConfig;
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
@@ -38,6 +39,7 @@
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.Trace;
+import android.text.FontConfig;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -45,7 +47,6 @@
 import android.util.Slog;
 import android.util.TypedValue;
 import android.util.Xml;
-import android.view.Display;
 import android.view.DisplayAdjustments;
 
 import java.io.IOException;
@@ -420,7 +421,7 @@
                         mConfiguration.smallestScreenWidthDp,
                         mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
                         mConfiguration.screenLayout, mConfiguration.uiMode,
-                        Build.VERSION.RESOURCES_SDK_INT);
+                        mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
 
                 if (DEBUG_CONFIG) {
                     Slog.i(TAG, "**** Updating config of " + this + ": final config is "
@@ -745,13 +746,17 @@
      * Loads a font from XML or resources stream.
      */
     @Nullable
-    public Typeface loadFont(TypedValue value, int id) {
+    public Typeface loadFont(Resources wrapper, TypedValue value, int id) {
         if (value.string == null) {
             throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
                     + Integer.toHexString(id) + ") is not a Font: " + value);
         }
 
         final String file = value.string.toString();
+        Typeface cached = Typeface.createFromCache(mAssets, file);
+        if (cached != null) {
+            return cached;
+        }
 
         if (DEBUG_LOAD) {
             Log.v(TAG, "Loading font for cookie " + value.assetCookie + ": " + file);
@@ -759,12 +764,17 @@
 
         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
         try {
-            if (file.endsWith(".xml")) {
-                // TODO handle xml type font definitions
-            } else {
-                return Typeface.createFromResources(
-                        mAssets, value.string.toString(), value.assetCookie);
+            if (file.endsWith("xml")) {
+                final XmlResourceParser rp = loadXmlResourceParser(
+                        file, id, value.assetCookie, "font");
+                final FontConfig config = FontResourcesParser.parse(rp, wrapper);
+                return Typeface.createFromResources(config, mAssets, file);
             }
+            return Typeface.createFromResources(mAssets, file, value.assetCookie);
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "Failed to parse xml resource " + file, e);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to read xml resource " + file, e);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
@@ -1197,7 +1207,7 @@
 
         @Config int getChangingConfigurations() {
             synchronized (mKey) {
-                final int nativeChangingConfig =
+                final @NativeConfig int nativeChangingConfig =
                         AssetManager.getThemeChangingConfigurations(mTheme);
                 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
             }
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 3912201..f48afb5 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -22,6 +22,7 @@
 import android.annotation.StyleableRes;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.StrictMode;
 import android.util.AttributeSet;
@@ -937,6 +938,36 @@
     }
 
     /**
+     * Retrieve the Typeface for the attribute at <var>index</var>.
+     * <p>
+     * This method will throw an exception if the attribute is defined but is
+     * not a font.
+     *
+     * @param index Index of attribute to retrieve.
+     *
+     * @return Typeface for the attribute, or {@code null} if not defined.
+     * @throws RuntimeException if the TypedArray has already been recycled.
+     * @throws UnsupportedOperationException if the attribute is defined but is
+     *         not a font resource.
+     */
+    @Nullable
+    public Typeface getFont(@StyleableRes int index) {
+        if (mRecycled) {
+            throw new RuntimeException("Cannot make calls to a recycled instance!");
+        }
+
+        final TypedValue value = mValue;
+        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+            if (value.type == TypedValue.TYPE_ATTRIBUTE) {
+                throw new UnsupportedOperationException(
+                        "Failed to resolve attribute at index " + index + ": " + value);
+            }
+            return mResources.getFont(value, value.resourceId);
+        }
+        return null;
+    }
+
+    /**
      * Retrieve the CharSequence[] for the attribute at <var>index</var>.
      * This gets the resource ID of the selected attribute, and uses
      * {@link Resources#getTextArray Resources.getTextArray} of the owning
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 79eac26..1887086 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -792,7 +792,8 @@
     private Location getGpsLocation() {
         String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
         double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES);
-        Long timeStamp = get(CaptureResult.JPEG_GPS_TIMESTAMP);
+        // Location expects timestamp in [ms.]
+        Long timeStamp = get(CaptureResult.JPEG_GPS_TIMESTAMP) * 1000;
 
         if (areValuesAllNull(processingMethod, coords, timeStamp)) {
             return null;
@@ -823,7 +824,8 @@
 
         double[] coords = { l.getLatitude(), l.getLongitude(), l.getAltitude() };
         String processMethod = translateLocationProviderToProcess(l.getProvider());
-        long timestamp = l.getTime();
+        //JPEG_GPS_TIMESTAMP expects sec. instead of msec.
+        long timestamp = l.getTime() / 1000;
 
         set(CaptureRequest.JPEG_GPS_TIMESTAMP, timestamp);
         set(CaptureRequest.JPEG_GPS_COORDINATES, coords);
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 12e1963..33a9f5e 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -108,13 +108,14 @@
      * </p>
      *
      * <p>
-     * A private virtual display belongs to the application that created it.
-     * Only the a owner of a private virtual display is allowed to place windows upon it.
-     * The private virtual display also does not participate in display mirroring: it will
-     * neither receive mirrored content from another display nor allow its own content to
-     * be mirrored elsewhere.  More precisely, the only processes that are allowed to
-     * enumerate or interact with the private display are those that have the same UID as the
-     * application that originally created the private virtual display.
+     * A private virtual display belongs to the application that created it.  Only the a owner of a
+     * private virtual display and the apps that are already on that display are allowed to place
+     * windows upon it.  The private virtual display also does not participate in display mirroring:
+     * it will neither receive mirrored content from another display nor allow its own content to be
+     * mirrored elsewhere.  More precisely, the only processes that are allowed to enumerate or
+     * interact with the private display are those that have the same UID as the application that
+     * originally created the private virtual display or as the activities that are already on that
+     * display.
      * </p>
      *
      * @see #createVirtualDisplay
@@ -234,6 +235,21 @@
      */
     public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 1 << 4;
 
+    /**
+     * Virtual display flag: Allows content to be displayed on private virtual displays when
+     * keyguard is shown but is insecure.
+     *
+     * <p>
+     * This flag can only be applied to private displays as defined by the
+     * {@link Display#FLAG_PRIVATE} display flag. It is mutually exclusive with
+     * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}. If both flags are specified then this flag's behavior
+     * will not be applied.
+     * </p>
+     *
+     * @see #createVirtualDisplay
+     */
+    public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5;
+
     /** @hide */
     public DisplayManager(Context context) {
         mContext = context;
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 2bf841c..0ee2574 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -102,10 +102,13 @@
 
     /**
      * @param tag From your MetricsEvent enum.
-     * @param value One of Integer, Long, Float, String
-     * @return
+     * @param value One of Integer, Long, Float, or String; or null to clear the tag.
+     * @return modified LogMaker
      */
     public LogMaker addTaggedData(int tag, Object value) {
+        if (value == null) {
+            return clearTaggedData(tag);
+        }
         if (!isValidValue(value)) {
             throw new IllegalArgumentException(
                     "Value must be loggable type - int, long, float, String");
@@ -118,11 +121,21 @@
         return this;
     }
 
+    /**
+     * Remove a value from the LogMaker.
+     *
+     * @param tag From your MetricsEvent enum.
+     * @return modified LogMaker
+     */
+    public LogMaker clearTaggedData(int tag) {
+        entries.delete(tag);
+        return this;
+    }
+
+    /**
+     * @return true if this object may be added to a LogMaker as a value.
+     */
     public boolean isValidValue(Object value) {
-        if (value == null) {
-            Log.i("LogBuilder", "Logging a null value.");
-            return true;
-        }
         return value instanceof Integer ||
             value instanceof String ||
             value instanceof Long ||
@@ -227,6 +240,9 @@
         return out;
     }
 
+    /**
+     * Reconstitute an object from the output of {@link #serialize()}.
+     */
     public void deserialize(Object[] items) {
         int i = 0;
         while (i < items.length) {
@@ -239,4 +255,22 @@
             }
         }
     }
+
+    /**
+     * @param that the object to compare to.
+     * @return true if values in that equal values in this, for tags that exist in this.
+     */
+    public boolean isSubsetOf(LogMaker that) {
+        if (that == null) {
+            return false;
+        }
+        for (int i = 0; i < entries.size(); i++) {
+            int key = this.entries.keyAt(i);
+            Object thisValue = this.entries.valueAt(i);
+            Object thatValue = that.entries.get(key);
+            if ((thisValue == null && thatValue != null) || !thisValue.equals(thatValue))
+                return false;
+        }
+        return true;
+    }
 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 80ecf97..817cb5b 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -883,8 +883,11 @@
             SystemProperties.getInt("ro.debuggable", 0) == 1;
 
     /** {@hide} */
-    public static final boolean IS_ENG =
-            "eng".equals(getString("ro.build.type"));
+    public static final boolean IS_ENG = "eng".equals(TYPE);
+    /** {@hide} */
+    public static final boolean IS_USERDEBUG = "userdebug".equals(TYPE);
+    /** {@hide} */
+    public static final boolean IS_USER = "user".equals(TYPE);
 
     /**
      * Whether this build is running inside a container.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 8d9ceb6..7cdb3ce 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -272,6 +272,11 @@
     }
 
     /** {@hide} */
+    public static File getDataMiscCeDirectory() {
+        return buildPath(getDataDirectory(), "misc_ce");
+    }
+
+    /** {@hide} */
     public static File getDataMiscCeDirectory(int userId) {
         return buildPath(getDataDirectory(), "misc_ce", String.valueOf(userId));
     }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index d6688e3..a41f45b 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -189,6 +189,13 @@
     /** {@hide} */
     public static final int LAST_APPLICATION_CACHE_GID = 29999;
 
+    /** {@hide} */
+    public static final int MEDIA_AUDIO_GID = 1055;
+    /** {@hide} */
+    public static final int MEDIA_VIDEO_GID = 1056;
+    /** {@hide} */
+    public static final int MEDIA_IMAGE_GID = 1057;
+
     /**
      * Standard priority of application threads.
      * Use with {@link #setThreadPriority(int)} and
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index ae981b7..b9e4bad 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -40,6 +40,7 @@
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
 import dalvik.system.VMDebug;
+import dalvik.system.VMRuntime;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -423,7 +424,21 @@
              * disk operations but will likely expand in future releases.
              */
             public Builder detectAll() {
-                return enable(ALL_THREAD_DETECT_BITS);
+                detectDiskReads();
+                detectDiskWrites();
+                detectNetwork();
+
+                final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
+                if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
+                    detectCustomSlowCalls();
+                }
+                if (targetSdk >= Build.VERSION_CODES.M) {
+                    detectResourceMismatches();
+                }
+                if (targetSdk >= Build.VERSION_CODES.O) {
+                    detectUnbufferedIo();
+                }
+                return this;
             }
 
             /**
@@ -722,18 +737,31 @@
              * but will likely expand in future releases.
              */
             public Builder detectAll() {
-                int flags = DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_CURSOR_LEAKS
-                        | DETECT_VM_CLOSABLE_LEAKS | DETECT_VM_REGISTRATION_LEAKS
-                        | DETECT_VM_FILE_URI_EXPOSURE | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION
-                        | DETECT_VM_UNTAGGED_SOCKET;
+                detectLeakedSqlLiteObjects();
 
-                // TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have facility
-                // for apps to mark sockets that should be ignored
-                if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) {
-                    flags |= DETECT_VM_CLEARTEXT_NETWORK;
+                final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
+                if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
+                    detectActivityLeaks();
+                    detectLeakedClosableObjects();
                 }
-
-                return enable(flags);
+                if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
+                    detectLeakedRegistrationObjects();
+                }
+                if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+                    detectFileUriExposure();
+                }
+                if (targetSdk >= Build.VERSION_CODES.M) {
+                    // TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have
+                    // facility for apps to mark sockets that should be ignored
+                    if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) {
+                        detectCleartextNetwork();
+                    }
+                }
+                if (targetSdk >= Build.VERSION_CODES.O) {
+                    detectContentUriWithoutPermission();
+                    detectUntaggedSockets();
+                }
+                return this;
             }
 
             /**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c5c380c..388054d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -66,6 +66,8 @@
     private final IUserManager mService;
     private final Context mContext;
 
+    private Boolean mIsManagedProfileCached;
+
     /**
      * @hide
      * No user restriction.
@@ -970,8 +972,14 @@
      */
     @SystemApi
     public boolean isManagedProfile() {
+        // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
+        // Worst case we might end up calling the AIDL method multiple times but that's fine.
+        if (mIsManagedProfileCached != null) {
+            return mIsManagedProfileCached;
+        }
         try {
-            return mService.isManagedProfile(UserHandle.myUserId());
+            mIsManagedProfileCached = mService.isManagedProfile(UserHandle.myUserId());
+            return mIsManagedProfileCached;
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -987,6 +995,9 @@
      */
     @SystemApi
     public boolean isManagedProfile(@UserIdInt int userId) {
+        if (userId == UserHandle.myUserId()) {
+            return isManagedProfile();
+        }
         try {
             return mService.isManagedProfile(userId);
         } catch (RemoteException re) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d41b3c5..f1bffd3 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1274,10 +1274,14 @@
 
     /**
      * Activity Action: Show notification settings for a single app.
-     *
-     * Input: Optionally, {@link #EXTRA_CHANNEL_ID}, to highlight that channel.
+     * <p>
+     *     Input: {@link #EXTRA_APP_PACKAGE}, the package containing the channel to display.
+     *     Input: Optionally, {@link #EXTRA_CHANNEL_ID}, to highlight that channel.
+     * <p>
+     * Output: Nothing.
      * @hide
      */
+    @SystemApi
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_APP_NOTIFICATION_SETTINGS
             = "android.settings.APP_NOTIFICATION_SETTINGS";
@@ -7654,6 +7658,13 @@
        public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
                 "location_background_throttle_interval_ms";
 
+        /**
+         * Packages that are whitelisted for background throttling (throttling will not be applied).
+         * @hide
+         */
+        public static final String LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST =
+            "location_background_throttle_package_whitelist";
+
        /**
         * Whether TV will switch to MHL port when a mobile device is plugged in.
         * (0 = false, 1 = true)
@@ -8174,6 +8185,14 @@
         public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
 
         /**
+         * Value to specify whether network quality scores and badging should be shown in the UI.
+         *
+         * Type: int (0 for false, 1 for true)
+         * @hide
+         */
+        public static final String NETWORK_SCORING_UI_ENABLED = "network_scoring_ui_enabled";
+
+        /**
          * Value to specify if network recommendations from
          * {@link com.android.server.NetworkScoreService} are enabled.
          *
@@ -9562,6 +9581,14 @@
         public static final String CONTACTS_DATABASE_WAL_ENABLED = "contacts_database_wal_enabled";
 
         /**
+         * Flag to enable the link to location permissions in location setting. Set to 0 to disable.
+         *
+         * @hide
+         */
+        public static final String LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED =
+                "location_settings_link_to_permissions_enabled";
+
+        /**
          * Settings to backup. This is here so that it's in the same place as the settings
          * keys and easy to update.
          *
@@ -10112,6 +10139,12 @@
          * @hide
          */
         public static final String WARNING_TEMPERATURE = "warning_temperature";
+
+        /**
+         * Whether the diskstats logging task is enabled/disabled.
+         * @hide
+         */
+        public static final String ENABLE_DISKSTATS_LOGGING = "enable_diskstats_logging";
     }
 
     /**
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index b5cb8f8..1e4f90d 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -89,16 +89,14 @@
 
     /**
      * Key of the {@link Bundle} passed to methods such as
-     * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
-     * containing the extras set by
+     * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by
      * {@link android.view.autofill.FillResponse.Builder#setExtras(Bundle)}.
      */
     public static final String EXTRA_RESPONSE_EXTRAS = KEY_PREFIX + "RESPONSE_EXTRAS";
 
     /**
      * Key of the {@link Bundle} passed to methods such as
-     * {@link #onSaveRequest(AssistStructure, Bundle, CancellationSignal, SaveCallback)}
-     * containing the extras set by
+     * {@link #onSaveRequest(AssistStructure, Bundle, SaveCallback)} containing the extras set by
      * {@link android.view.autofill.Dataset.Builder#setExtras(Bundle)}.
      */
     public static final String EXTRA_DATASET_EXTRAS = KEY_PREFIX + "DATASET_EXTRAS";
@@ -134,9 +132,9 @@
 
         @Override
         public void autoFill(AssistStructure structure, IAutoFillServerCallback callback,
-                Bundle extras, int flags) {
+                int flags) {
             mHandlerCaller
-                    .obtainMessageIOOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, extras, callback)
+                    .obtainMessageIOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, callback)
                     .sendToTarget();
         }
 
@@ -179,9 +177,8 @@
                     final SomeArgs args = (SomeArgs) msg.obj;
                     final int flags = msg.arg1;
                     final AssistStructure structure = (AssistStructure) args.arg1;
-                    final Bundle extras = (Bundle) args.arg2;
-                    final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg3;
-                    requestAutoFill(callback, structure, extras, flags);
+                    final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg2;
+                    requestAutoFill(callback, structure, flags);
                     break;
                 } case MSG_AUTHENTICATE_FILL_RESPONSE: {
                     final int flags = msg.arg1;
@@ -254,8 +251,8 @@
      * @param cancellationSignal signal for observing cancel requests.
      * @param callback object used to notify the result of the request.
      */
-    public abstract void onFillRequest(AssistStructure structure,
-            Bundle data, CancellationSignal cancellationSignal, FillCallback callback);
+    public abstract void onFillRequest(AssistStructure structure, Bundle data,
+            CancellationSignal cancellationSignal, FillCallback callback);
 
     /**
      * Called when user requests service to save the fields of an {@link Activity}.
@@ -267,20 +264,19 @@
      * @param structure {@link Activity}'s view structure.
      * @param data bundle containing additional arguments set by the Android system (currently none)
      * or data passed by the service in the {@link FillResponse} that originated this call.
-     * @param cancellationSignal signal for observing cancel requests.
      * @param callback object used to notify the result of the request.
      */
-    public abstract void onSaveRequest(AssistStructure structure,
-            Bundle data, CancellationSignal cancellationSignal, SaveCallback callback);
+    public abstract void onSaveRequest(AssistStructure structure, Bundle data,
+            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.
+     * {@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(
@@ -336,27 +332,28 @@
     }
 
     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;
-            default:
-                Log.w(TAG, "invalid flag on requestAutoFill(): " + flags);
+            int flags) {
+        if (DEBUG) Log.d(TAG, "requestAutoFill(): flags=" + flags);
+
+        if ((flags & AUTO_FILL_FLAG_TYPE_FILL) != 0) {
+            final FillCallback fillCallback = new FillCallback(callback);
+            if (DEBUG_PENDING_CALLBACKS) {
+                addPendingCallback(fillCallback);
+            }
+            // TODO(b/33197203): hook up the cancelationSignal
+            onFillRequest(structure, null, new CancellationSignal(), fillCallback);
+            return;
         }
+        if ((flags & AUTO_FILL_FLAG_TYPE_SAVE) != 0) {
+            final SaveCallback saveCallback = new SaveCallback(callback);
+            if (DEBUG_PENDING_CALLBACKS) {
+                addPendingCallback(saveCallback);
+            }
+            onSaveRequest(structure, null, saveCallback);
+            return;
+        }
+
+        Log.w(TAG, "invalid flags on requestAutoFill(): " + flags);
     }
 
     private void addPendingCallback(CallbackHelper.Dumpable callback) {
diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
index cc83776..8c3898a 100644
--- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
@@ -25,8 +25,7 @@
  *
  * @hide
  */
-// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
-// itself
+// TODO(b/33197203): rename IAutoFillAppSession
 oneway interface IAutoFillAppCallback {
     /**
       * Auto-fills the activity with the contents of a dataset.
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index ce42107..ace5411 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -27,8 +27,9 @@
  */
 oneway interface IAutoFillManagerService {
 
-    void showAutoFillInput(in AutoFillId id, in Rect boundaries);
+    // Called by AutoFillManager (app).
+    void requestAutoFill(in AutoFillId id, in Rect bounds, int flags);
 
-    // TODO(b/33197203): remove it and refactor onShellCommand
-    void requestAutoFill(IBinder activityToken, int userId, in Bundle extras, int flags);
+    // Called by ShellCommand only.
+    void requestAutoFillForUser(int userId, int flags);
 }
diff --git a/core/java/android/service/autofill/IAutoFillServerCallback.aidl b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
index 185c8f3..f7d5064 100644
--- a/core/java/android/service/autofill/IAutoFillServerCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
@@ -28,8 +28,7 @@
  *
  * @hide
  */
-// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
-// itself
+// TODO(b/33197203): rename to IAutoFillServerSession
 oneway interface IAutoFillServerCallback {
     // TODO(b/33197203): document methods
     void showResponse(in FillResponse response);
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index fa9786a..3e8087b 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -27,8 +27,7 @@
 // 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 autoFill(in AssistStructure structure, in IAutoFillServerCallback callback, int flags);
     void authenticateFillResponse(in Bundle extras, int flags);
     void authenticateDataset(in Bundle extras, int flags);
     void onConnected();
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index d5022d8..e2fb588 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -58,7 +58,7 @@
     /**
      * Notifies the Android System that an
      * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
-     * android.os.CancellationSignal, SaveCallback)} was successfully fulfilled by the service.
+     * SaveCallback)} was successfully fulfilled by the service.
      *
      * @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved.
      *
@@ -85,7 +85,7 @@
     /**
      * 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.
+     * SaveCallback)} could not be fulfilled by the service.
      *
      * @param message error message to be displayed to the user.
      *
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index df694ff..3048a38 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -164,7 +164,7 @@
      * Class that holds information about a Font.
      */
     public static final class Font implements Parcelable {
-        private final String mFontName;
+        private String mFontName;
         private final int mTtcIndex;
         private final List<Axis> mAxes;
         private final int mWeight;
@@ -203,6 +203,13 @@
         }
 
         /**
+         * @hide
+         */
+        public void setFontName(String fontName) {
+            mFontName = fontName;
+        }
+
+        /**
          * Returns the index to be used to access this font when accessing a TTC file.
          */
         public int getTtcIndex() {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 105cc47..3ba55ed 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -163,7 +163,7 @@
 
     /**
      * Display flag: Indicates that the display is private.  Only the application that
-     * owns the display can create windows on it.
+     * owns the display and apps that are already on the display can create windows on it.
      *
      * @see #getFlags
      */
@@ -194,6 +194,19 @@
     public static final int FLAG_ROUND = 1 << 4;
 
     /**
+     * Display flag: Indicates that the display can show its content when non-secure keyguard is
+     * shown.
+     * <p>
+     * This flag identifies secondary displays that won't show keyguard if it can be dismissed
+     * without entering credentials. Display content will be shown even if other displays are
+     * locked.
+     * </p>
+     *
+     * @see #getFlags
+     */
+    public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5;
+
+    /**
      * Display flag: Indicates that the contents of the display should not be scaled
      * to fit the physical screen dimensions.  Used for development only to emulate
      * devices with smaller physicals screens while preserving density.
@@ -777,8 +790,7 @@
     public boolean isHdr() {
         synchronized (this) {
             updateDisplayInfoLocked();
-            int[] types = mDisplayInfo.hdrCapabilities.getSupportedHdrTypes();
-            return types != null && types.length > 0;
+            return mDisplayInfo.isHdr();
         }
     }
 
@@ -788,12 +800,7 @@
     public boolean isWideColorGamut() {
         synchronized (this) {
             updateDisplayInfoLocked();
-            for (int colorMode : mDisplayInfo.supportedColorModes) {
-                if (colorMode == COLOR_MODE_DCI_P3 || colorMode > COLOR_MODE_SRGB) {
-                    return true;
-                }
-            }
-            return false;
+            return mDisplayInfo.isWideColorGamut();
         }
     }
 
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 1aef6ec..f6b94af 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -519,6 +519,20 @@
                 logicalHeight : logicalWidth;
     }
 
+    public boolean isHdr() {
+        int[] types = hdrCapabilities != null ? hdrCapabilities.getSupportedHdrTypes() : null;
+        return types != null && types.length > 0;
+    }
+
+    public boolean isWideColorGamut() {
+        for (int colorMode : supportedColorModes) {
+            if (colorMode == Display.COLOR_MODE_DCI_P3 || colorMode > Display.COLOR_MODE_SRGB) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns true if the specified UID has access to this display.
      */
diff --git a/core/java/android/view/IDockedStackListener.aidl b/core/java/android/view/IDockedStackListener.aidl
index 36a81db..4cf7cf3e 100644
--- a/core/java/android/view/IDockedStackListener.aidl
+++ b/core/java/android/view/IDockedStackListener.aidl
@@ -40,8 +40,11 @@
      *
      * @param minimized Whether the docked stack is currently minimized.
      * @param animDuration The duration of the animation for changing the minimized state.
+     * @param isHomeStackResizable If the home stack is resizable, a portion of the docked stack
+     *        will be shown with the divider
      */
-    void onDockedStackMinimizedChanged(boolean minimized, long animDuration);
+    void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
+            boolean isHomeStackResizable);
 
     /**
      * Called when window manager decides to adjust the divider for IME. Like the minimized state,
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a12600a..5bb577f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -34,7 +34,7 @@
     private static final String TAG = "SurfaceControl";
 
     private static native long nativeCreate(SurfaceSession session, String name,
-            int w, int h, int format, int flags)
+            int w, int h, int format, int flags, long parentObject)
             throws OutOfResourcesException;
     private static native void nativeRelease(long nativeObject);
     private static native void nativeDestroy(long nativeObject);
@@ -287,6 +287,12 @@
     public SurfaceControl(SurfaceSession session,
             String name, int w, int h, int format, int flags)
                     throws OutOfResourcesException {
+        this(session, name, w, h, format, flags, null);
+    }
+
+    public SurfaceControl(SurfaceSession session,
+            String name, int w, int h, int format, int flags, SurfaceControl parent)
+                    throws OutOfResourcesException {
         if (session == null) {
             throw new IllegalArgumentException("session must not be null");
         }
@@ -304,7 +310,7 @@
         }
 
         mName = name;
-        mNativeObject = nativeCreate(session, name, w, h, format, flags);
+        mNativeObject = nativeCreate(session, name, w, h, format, flags, parent != null ? parent.mNativeObject : 0);
         if (mNativeObject == 0) {
             throw new OutOfResourcesException(
                     "Couldn't allocate SurfaceControl native object");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ba9bb67..e8535cdb 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -104,6 +104,7 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Transformation;
+import android.view.autofill.AutoFillManager;
 import android.view.autofill.AutoFillType;
 import android.view.autofill.AutoFillValue;
 import android.view.autofill.VirtualViewDelegate;
@@ -1765,12 +1766,6 @@
      */
     int mAccessibilityViewId = NO_ID;
 
-    /**
-     * The stable ID of this view for auto-fill purposes.
-     */
-    private int mAutoFillId = NO_ID;
-
-
     private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
 
     SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent;
@@ -4045,9 +4040,9 @@
      * input fields and tags (like {@code id}).
      * </ul>
      */
-    // TODO(b/33197203) (b/34078930): improve documentation: mention all cases, show examples, etc.
-    // In particular, be more specific about webview restrictions
-    public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x1;
+    // TODO(b/33197203): cannot conflict with flags defined on AutoFillManager until they're removed
+    // (when save is refactored).
+    public static final int AUTO_FILL_FLAG_TYPE_FILL = 0x10000000;
 
     /**
      * Set when the user explicitly asked a {@link android.service.autofill.AutoFillService} to save
@@ -4057,7 +4052,9 @@
      * (Personally Identifiable Information). For example, the text of password fields should be
      * included since that's what's typically saved.
      */
-    public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x2;
+    // TODO(b/33197203): cannot conflict with flags defined on AutoFillManager until they're removed
+    // (when save is refactored).
+    public static final int AUTO_FILL_FLAG_TYPE_SAVE = 0x20000000;
 
     /**
      * Set to true when drawing cache is enabled and cannot be created.
@@ -6393,6 +6390,14 @@
      * @see ViewGroup#getTouchscreenBlocksFocus()
      */
     public boolean hasFocusable() {
+        return hasFocusable(true);
+    }
+
+    /**
+     * @hide pending determination of whether this should be public or not.
+     * Currently used for compatibility with old focusability expectations in ListView.
+     */
+    public boolean hasFocusable(boolean allowAutoFocus) {
         if (!isFocusableInTouchMode()) {
             for (ViewParent p = mParent; p instanceof ViewGroup; p = p.getParent()) {
                 final ViewGroup g = (ViewGroup) p;
@@ -6401,7 +6406,10 @@
                 }
             }
         }
-        return (mViewFlags & VISIBILITY_MASK) == VISIBLE && isFocusable();
+        if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+            return false;
+        }
+        return allowAutoFocus ? getFocusable() != NOT_FOCUSABLE : getFocusable() == FOCUSABLE;
     }
 
     /**
@@ -6437,16 +6445,23 @@
             if (isPressed()) {
                 setPressed(false);
             }
-            if (imm != null && mAttachInfo != null
-                    && mAttachInfo.mHasWindowFocus) {
+            if (imm != null && mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
                 imm.focusOut(this);
             }
             onFocusLost();
-        } else if (imm != null && mAttachInfo != null
-                && mAttachInfo.mHasWindowFocus) {
+        } else if (imm != null && mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
             imm.focusIn(this);
         }
 
+        if (isAutoFillable()) {
+            AutoFillManager afm = getAutoFillManager();
+            if (afm != null) {
+                afm.updateAutoFillInput(this, gainFocus
+                        ? AutoFillManager.FLAG_UPDATE_UI_SHOW
+                        : AutoFillManager.FLAG_UPDATE_UI_HIDE);
+            }
+        }
+
         invalidate(true);
         ListenerInfo li = mListenerInfo;
         if (li != null && li.mOnFocusChangeListener != null) {
@@ -6940,8 +6955,7 @@
         if (forAutoFill) {
             // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
             // reuse the accessibility id to save space.
-            mAutoFillId = getAccessibilityViewId();
-            structure.setAutoFillId(mAutoFillId);
+            structure.setAutoFillId(getAccessibilityViewId());
             structure.setAutoFillType(getAutoFillType());
         }
 
@@ -7083,6 +7097,15 @@
         return null;
     }
 
+    @Nullable
+    private AutoFillManager getAutoFillManager() {
+        return mContext.getSystemService(AutoFillManager.class);
+    }
+
+    private boolean isAutoFillable() {
+        return getAutoFillType() != null && !isAutoFillBlocked();
+    }
+
     private void populateVirtualStructure(ViewStructure structure,
             AccessibilityNodeProvider provider, AccessibilityNodeInfo info, int flags) {
         // NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
@@ -7568,20 +7591,6 @@
     }
 
     /**
-     * Gets the unique identifier of this view for auto-fill purposes.
-     *
-     * <p>It's only set after {@link #onProvideAutoFillStructure(ViewStructure, int)} is called.
-     *
-     * @return The view autofill id or {@link #NO_ID} if
-     * {@link #onProvideAutoFillStructure(ViewStructure, int)}  was not called yet.
-     *
-     * @hide
-     */
-    public int getAutoFillViewId() {
-        return mAutoFillId;
-    }
-
-    /**
      * Gets the unique identifier of the window in which this View reseides.
      *
      * @return The window accessibility id.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index af39eb3..b135bef 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1105,13 +1105,15 @@
         return null;
     }
 
+    /** @hide Overriding hidden method */
     @Override
-    public boolean hasFocusable() {
+    public boolean hasFocusable(boolean allowAutoFocus) {
         if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
             return false;
         }
 
-        if (isFocusable()) {
+        // TODO This should probably be super.hasFocusable, but that would change behavior
+        if (allowAutoFocus ? getFocusable() != NOT_FOCUSABLE : getFocusable() == FOCUSABLE) {
             return true;
         }
 
@@ -1122,7 +1124,7 @@
 
             for (int i = 0; i < count; i++) {
                 final View child = children[i];
-                if (child.hasFocusable()) {
+                if (child.hasFocusable(allowAutoFocus)) {
                     return true;
                 }
             }
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
index cd9842f..cf56e0e 100644
--- a/core/java/android/view/autofill/AutoFillManager.java
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -36,12 +36,16 @@
     /**
      * Flag used to show the auto-fill UI affordance for a view.
      */
-    public static final int FLAG_UPDATE_UI_SHOW = 1 << 0;
+    // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
+    // save is refactored).
+    public static final int FLAG_UPDATE_UI_SHOW = 0x1;
 
     /**
      * Flag used to hide the auto-fill UI affordance for a view.
      */
-    public static final int FLAG_UPDATE_UI_HIDE = 1 << 1;
+    // TODO(b/33197203): cannot conflict with flags defined on View until they're removed (when
+    // save is refactored).
+    public static final int FLAG_UPDATE_UI_HIDE = 0x2;
 
     private final IAutoFillManagerService mService;
 
@@ -64,11 +68,10 @@
      * {@link #FLAG_UPDATE_UI_HIDE}.
      */
     public void updateAutoFillInput(View view, int flags) {
-        if (DEBUG) {
-            Log.v(TAG, "updateAutoFillInput(" + view.getAutoFillViewId() + "): flags=" + flags);
-        }
+        final Rect bounds = new Rect();
+        view.getBoundsOnScreen(bounds);
 
-        updateAutoFillInput(view, false, View.NO_ID, null, flags);
+        requestAutoFill(new AutoFillId(view.getAccessibilityViewId()), bounds, flags);
     }
 
     /**
@@ -79,56 +82,22 @@
      *
      * @param parent parent view.
      * @param childId id identifying the virtual child inside the parent view.
-     * @param boundaries boundaries of the child (inside the parent; could be {@code null} when
+     * @param bounds absolute boundaries of the child in the window (could be {@code null} when
      * flag is {@link #FLAG_UPDATE_UI_HIDE}.
      * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
      * {@link #FLAG_UPDATE_UI_HIDE}.
      */
-    public void updateAutoFillInput(View parent, int childId, @Nullable Rect boundaries,
+    public void updateAutoFillInput(View parent, int childId, @Nullable Rect bounds,
             int flags) {
+        requestAutoFill(new AutoFillId(parent.getAccessibilityViewId(), childId), bounds, flags);
+    }
+
+    private void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
         if (DEBUG) {
-            Log.v(TAG, "updateAutoFillInput(" + parent.getAutoFillViewId() + ", " + childId
-                    + "): boundaries=" + boundaries + ", flags=" + flags);
+            Log.v(TAG, "requestAutoFill(): id=" + id + ", bounds=" + bounds + ", flags=" + flags);
         }
-        updateAutoFillInput(parent, true, childId, boundaries, flags);
-    }
-
-    private void updateAutoFillInput(View view, boolean virtual, int childId, Rect boundaries,
-            int flags) {
-        if ((flags & FLAG_UPDATE_UI_SHOW) != 0) {
-            final int viewId = view.getAutoFillViewId();
-            final AutoFillId id = virtual
-                    ? new AutoFillId(viewId, childId)
-                    : new AutoFillId(viewId);
-            showAutoFillInput(id, boundaries);
-            return;
-        }
-        // TODO(b/33197203): handle FLAG_UPDATE_UI_HIDE
-    }
-
-    private void showAutoFillInput(AutoFillId id, Rect boundaries) {
-        final int autoFillViewId = id.getViewId();
-        /*
-         * TODO(b/33197203): currently SHOW_AUTO_FILL_BAR is only set once per activity (i.e, when
-         * the view does not have an auto-fill id), but it should be called again for views that
-         * were not part of the initial auto-fill dataset returned by the service. For example:
-         *
-         * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
-         * 2.User taps `first_name`.
-         * 3.Service returns a dataset with ids for `first_name` and `last_name`.
-         * 4.When user taps `first_name` (again) or `last_name`, flag should not have
-         *   SHOW_AUTO_FILL_BAR set, but when user taps `address`, it should (since that field was
-         *   not part of the initial dataset).
-         *
-         * Similarly, once the activity is auto-filled, the flag logic should be reset (so if the
-         * user taps the view again, a new auto-fill request is made)
-         */
-        if (autoFillViewId != View.NO_ID) {
-            return;
-        }
-
         try {
-            mService.showAutoFillInput(id, boundaries);
+            mService.requestAutoFill(id, bounds, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/autofill/AutoFillSession.java b/core/java/android/view/autofill/AutoFillSession.java
new file mode 100644
index 0000000..eec7a82
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillSession.java
@@ -0,0 +1,118 @@
+/*
+ * 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.view.autofill;
+
+import static android.view.autofill.Helper.DEBUG;
+
+import android.app.Activity;
+import android.os.RemoteException;
+import android.service.autofill.IAutoFillAppCallback;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * An auto-fill session associated with an activity.
+ *
+ * @hide
+ */
+public final class AutoFillSession {
+
+    private static final String TAG = "AutoFillSession";
+
+    private final IAutoFillAppCallback mCallback = new IAutoFillAppCallback.Stub() {
+        @Override
+        public void autoFill(Dataset dataset) throws RemoteException {
+            final Activity activity = mActivity.get();
+            if (activity == null) {
+                if (DEBUG) Log.d(TAG, "autoFill(): activity already GCed");
+                return;
+            }
+            // TODO(b/33197203): must keep the dataset so subsequent calls pass the same
+            // dataset.extras to service
+            activity.runOnUiThread(() -> {
+                final View root = activity.getWindow().getDecorView().getRootView();
+                for (DatasetField field : dataset.getFields()) {
+                    final AutoFillId id = field.getId();
+                    if (id == null) {
+                        Log.w(TAG, "autoFill(): null id on " + field);
+                        continue;
+                    }
+                    final int viewId = id.getViewId();
+                    final View view = root.findViewByAccessibilityIdTraversal(viewId);
+                    if (view == null) {
+                        Log.w(TAG, "autoFill(): no View with id " + viewId);
+                        continue;
+                    }
+
+                    // TODO(b/33197203): handle protected value (like credit card)
+                    if (id.isVirtual()) {
+                        // Delegate virtual fields.
+                        setAutoFillDelegateCallback();
+                        final VirtualViewDelegate delegate = view
+                                .getAutoFillVirtualViewDelegate(
+                                        mAutoFillDelegateCallback);
+                        if (delegate == null) {
+                            Log.w(TAG, "autoFill(): cannot fill virtual " + id
+                                    + "; no VirtualViewDelegate for view "
+                                    + view.getClass());
+                            continue;
+                        }
+                        if (DEBUG) {
+                            Log.d(TAG, "autoFill(): delegating " + id
+                                    + " to VirtualViewDelegate  " + delegate);
+                        }
+                        delegate.autoFill(id.getVirtualChildId(), field.getValue());
+                    } else {
+                        // Handle non-virtual fields itself.
+                        view.autoFill(field.getValue());
+                    }
+                }
+            });
+        }
+    };
+
+    private final WeakReference<Activity> mActivity;
+
+    @GuardedBy("this")
+    private VirtualViewDelegate.Callback mAutoFillDelegateCallback;
+
+    public AutoFillSession(Activity activity) {
+        mActivity = new WeakReference<>(activity);
+    }
+
+    public IAutoFillAppCallback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Lazily sets the {@link #mAutoFillDelegateCallback}.
+     */
+    private void setAutoFillDelegateCallback() {
+        synchronized (this) {
+            if (mAutoFillDelegateCallback == null) {
+                mAutoFillDelegateCallback = new VirtualViewDelegate.Callback() {
+                    // TODO(b/33197203): implement
+                };
+            }
+        }
+    }
+
+}
diff --git a/core/java/android/view/autofill/Dataset.java b/core/java/android/view/autofill/Dataset.java
index b11eecc..18a08f9 100644
--- a/core/java/android/view/autofill/Dataset.java
+++ b/core/java/android/view/autofill/Dataset.java
@@ -284,7 +284,7 @@
          * 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
+         * Bundle, 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.
diff --git a/core/java/android/view/autofill/FillResponse.java b/core/java/android/view/autofill/FillResponse.java
index 67eb85a..48dbb84 100644
--- a/core/java/android/view/autofill/FillResponse.java
+++ b/core/java/android/view/autofill/FillResponse.java
@@ -149,9 +149,9 @@
  * 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
+ * Bundle, 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 {
@@ -369,9 +369,8 @@
         /**
          * Adds ids of additional fields that the service would be interested to save (through
          * {@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)}.
+         * android.app.assist.AssistStructure, Bundle, android.service.autofill.SaveCallback)})
+         * but were not indirectly set through {@link #addDataset(Dataset)}.
          *
          * <p>See {@link FillResponse} for examples.
          */
@@ -386,8 +385,8 @@
          * 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.app.assist.AssistStructure, Bundle, 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.
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 729eb8d..2e2056c 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -97,7 +97,6 @@
     private final SpellCheckerInfo mSpellCheckerInfo;
     private final SpellCheckerSessionListener mSpellCheckerSessionListener;
     private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
-    private final SpellCheckerSubtype mSubtype;
 
     private boolean mIsUsed;
 
@@ -121,8 +120,7 @@
      * @hide
      */
     public SpellCheckerSession(
-            SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener,
-            SpellCheckerSubtype subtype) {
+            SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener) {
         if (info == null || listener == null || tsm == null) {
             throw new NullPointerException();
         }
@@ -132,7 +130,6 @@
         mTextServicesManager = tsm;
         mIsUsed = true;
         mSpellCheckerSessionListener = listener;
-        mSubtype = subtype;
     }
 
     /**
@@ -218,7 +215,8 @@
         mSpellCheckerSessionListener.onGetSentenceSuggestions(suggestionInfos);
     }
 
-    private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub {
+    private static final class SpellCheckerSessionListenerImpl
+            extends ISpellCheckerSessionListener.Stub {
         private static final int TASK_CANCEL = 1;
         private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
         private static final int TASK_CLOSE = 3;
@@ -366,7 +364,7 @@
             }
         }
 
-        public synchronized void onServiceConnected(ISpellCheckerSession session) {
+        public void onServiceConnected(ISpellCheckerSession session) {
             synchronized (this) {
                 switch (mState) {
                     case STATE_WAIT_CONNECTION:
@@ -408,9 +406,9 @@
                             + Integer.toHexString(mISpellCheckerSession.hashCode())
                             + " mPendingTasks.size()=" + mPendingTasks.size());
                 }
-            }
-            while (!mPendingTasks.isEmpty()) {
-                processTask(session, mPendingTasks.poll(), false);
+                while (!mPendingTasks.isEmpty()) {
+                    processTask(session, mPendingTasks.poll(), false);
+                }
             }
         }
 
@@ -529,7 +527,7 @@
         public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results);
     }
 
-    private static class InternalListener extends ITextServicesSessionListener.Stub {
+    private static final class InternalListener extends ITextServicesSessionListener.Stub {
         private final SpellCheckerSessionListenerImpl mParentSpellCheckerSessionListenerImpl;
 
         public InternalListener(SpellCheckerSessionListenerImpl spellCheckerSessionListenerImpl) {
@@ -547,7 +545,7 @@
         super.finalize();
         if (mIsUsed) {
             Log.e(TAG, "SpellCheckerSession was not finished properly." +
-                    "You should call finishShession() when you finished to use a spell checker.");
+                    "You should call finishSession() when you finished to use a spell checker.");
             close();
         }
     }
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 0f168f3..b4e6c56 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -171,8 +171,7 @@
         if (subtypeInUse == null) {
             return null;
         }
-        final SpellCheckerSession session = new SpellCheckerSession(
-                sci, mService, listener, subtypeInUse);
+        final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
         try {
             mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
                     session.getTextServicesSessionListener(),
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 8cedb17..47c4cf38 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2548,7 +2548,7 @@
     }
 
     private boolean isItemClickable(View view) {
-        return !view.hasFocusable();
+        return !view.hasFocusable(false);
     }
 
     /**
@@ -2824,7 +2824,7 @@
             final View v = getChildAt(mSelectedPosition - mFirstPosition);
 
             if (v != null) {
-                if (v.hasFocusable()) return;
+                if (v.hasFocusable(false)) return;
                 v.setPressed(true);
             }
             setPressed(true);
@@ -3428,7 +3428,7 @@
             if (mTouchMode == TOUCH_MODE_DOWN) {
                 mTouchMode = TOUCH_MODE_TAP;
                 final View child = getChildAt(mMotionPosition - mFirstPosition);
-                if (child != null && !child.hasFocusable()) {
+                if (child != null && !child.hasFocusable(false)) {
                     mLayoutMode = LAYOUT_NORMAL;
 
                     if (!mDataChanged) {
@@ -4005,7 +4005,7 @@
 
                 final float x = ev.getX();
                 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
-                if (inList && !child.hasFocusable()) {
+                if (inList && !child.hasFocusable(false)) {
                     if (mPerformClick == null) {
                         mPerformClick = new PerformClick();
                     }
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 6706790..07ad872 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -24,10 +24,7 @@
 import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.MovementMethod;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.autofill.AutoFillType;
-import android.view.autofill.AutoFillValue;
 
 /*
  * This is supposed to be a *very* thin veneer over TextView.
@@ -158,24 +155,4 @@
             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
         }
     }
-
-    // TODO(b/33197203): add unit/CTS tests for auto-fill methods
-
-    @Override
-    public void autoFill(AutoFillValue value) {
-        final CharSequence text = value.getTextValue();
-
-        if (text == null) {
-            Log.w(VIEW_LOG_TAG, "EditText.autoFill(): no text on AutoFillValue");
-            return;
-        }
-        setText(text);
-    }
-
-    @Override
-    public AutoFillType getAutoFillType() {
-        // TODO(b/33197203): ideally it should return a constant, but value returned by
-        // getInputType() can change.
-        return AutoFillType.forText(getInputType());
-    }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 072fe4a..8ec52bd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -137,6 +137,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.AnimationUtils;
 import android.view.autofill.AutoFillManager;
+import android.view.autofill.AutoFillType;
+import android.view.autofill.AutoFillValue;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
@@ -785,6 +787,7 @@
         ColorStateList textColorLink = null;
         int textSize = 15;
         String fontFamily = null;
+        Typeface fontTypeface = null;
         boolean fontFamilyExplicit = false;
         int typefaceIndex = -1;
         int styleIndex = -1;
@@ -847,7 +850,14 @@
                         break;
 
                     case com.android.internal.R.styleable.TextAppearance_fontFamily:
-                        fontFamily = appearance.getString(attr);
+                        try {
+                            fontTypeface = appearance.getFont(attr);
+                        } catch (UnsupportedOperationException e) {
+                            // Expected if it is not a font resource.
+                        }
+                        if (fontTypeface == null) {
+                            fontFamily = appearance.getString(attr);
+                        }
                         break;
 
                     case com.android.internal.R.styleable.TextAppearance_textStyle:
@@ -1151,7 +1161,14 @@
                     break;
 
                 case com.android.internal.R.styleable.TextView_fontFamily:
-                    fontFamily = a.getString(attr);
+                    try {
+                        fontTypeface = appearance.getFont(attr);
+                    } catch (UnsupportedOperationException e) {
+                        // Expected if it is not a font resource.
+                    }
+                    if (fontTypeface == null) {
+                        fontFamily = appearance.getString(attr);
+                    }
                     fontFamilyExplicit = true;
                     break;
 
@@ -1501,7 +1518,7 @@
         if (typefaceIndex != -1 && !fontFamilyExplicit) {
             fontFamily = null;
         }
-        setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
+        setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
 
         if (shadowcolor != 0) {
             setShadowLayer(r, dx, dy, shadowcolor);
@@ -1794,14 +1811,15 @@
         }
     }
 
-    private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
-        Typeface tf = null;
-        if (familyName != null) {
+    private void setTypefaceFromAttrs(Typeface fontTypeface, String familyName, int typefaceIndex,
+            int styleIndex) {
+        Typeface tf = fontTypeface;
+        if (tf == null && familyName != null) {
             tf = Typeface.create(familyName, styleIndex);
-            if (tf != null) {
-                setTypeface(tf);
-                return;
-            }
+        }
+        if (tf != null) {
+            setTypeface(tf);
+            return;
         }
         switch (typefaceIndex) {
             case SANS:
@@ -3099,10 +3117,19 @@
             setLinkTextColor(textColorLink);
         }
 
-        final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
+        Typeface fontTypeface = null;
+        String fontFamily = null;
+        try {
+            fontTypeface = ta.getFont(R.styleable.TextAppearance_fontFamily);
+        } catch (UnsupportedOperationException e) {
+            // Expected if it is not a font resource.
+        }
+        if (fontTypeface == null) {
+            fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
+        }
         final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
         final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
-        setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
+        setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
 
         final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
         if (shadowColor != 0) {
@@ -5163,15 +5190,15 @@
         boolean forceUpdate = false;
         if (isPassword) {
             setTransformationMethod(PasswordTransformationMethod.getInstance());
-            setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
+            setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
         } else if (isVisiblePassword) {
             if (mTransformation == PasswordTransformationMethod.getInstance()) {
                 forceUpdate = true;
             }
-            setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
+            setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
         } else if (wasPassword || wasVisiblePassword) {
             // not in password mode, clean up typeface and transformation
-            setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
+            setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, -1, -1);
             if (mTransformation == PasswordTransformationMethod.getInstance()) {
                 forceUpdate = true;
             }
@@ -9026,15 +9053,6 @@
                 Spannable sp = (Spannable) mText;
                 MetaKeyKeyListener.resetMetaState(sp);
             }
-        } else {
-            final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
-            if (afm != null) {
-                if (DEBUG_AUTOFILL) {
-                    Log.v(LOG_TAG, "onFocusChanged(): id=" + getAutoFillViewId() + ", focused= "
-                            + focused);
-                }
-                afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_HIDE);
-            }
         }
 
         startStopMarquee(focused);
@@ -9754,6 +9772,23 @@
         structure.setHint(getHint());
     }
 
+    // TODO(b/33197203): add unit/CTS tests for auto-fill methods
+
+    @Override
+    public void autoFill(AutoFillValue value) {
+        final CharSequence text = value.getTextValue();
+
+        if (text != null && isTextEditable()) {
+            setText(text);
+        }
+    }
+
+    @Override
+    @Nullable
+    public AutoFillType getAutoFillType() {
+        return isTextEditable() ? AutoFillType.forText(getInputType()) : null;
+    }
+
     /** @hide */
     @Override
     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
@@ -10615,7 +10650,7 @@
     protected void viewClicked(InputMethodManager imm) {
         final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
         if (afm != null) {
-            if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAutoFillViewId());
+            if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAccessibilityViewId());
 
             // TODO(b/33197203): integrate with onFocus and/or move to view?
             afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_SHOW);
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index a1eac36..81db93d 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -69,5 +69,6 @@
     boolean isBoundWidgetPackage(String packageName, int userId);
     boolean requestPinAppWidget(String packageName, in ComponentName providerComponent,
             in IntentSender resultIntent);
+    boolean isRequestPinAppWidgetSupported();
 }
 
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index c08cd72..3e231d0 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -266,43 +266,6 @@
         }
     }
 
-    private static InputMethodListBuilder getMinimumKeyboardSetWithoutSystemLocale(
-            final ArrayList<InputMethodInfo> imis, final Context context,
-            @Nullable final Locale fallbackLocale) {
-        // Before the system becomes ready, we pick up at least one keyboard in the following order.
-        // The first user (device owner) falls into this category.
-        // 1. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true
-        // 2. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true
-        // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false
-        // 4. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false
-        // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
-
-        final InputMethodListBuilder builder = new InputMethodListBuilder();
-        builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
-                true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
-        if (!builder.isEmpty()) {
-            return builder;
-        }
-        builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
-                true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
-        if (!builder.isEmpty()) {
-            return builder;
-        }
-        builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
-                false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
-        if (!builder.isEmpty()) {
-            return builder;
-        }
-        builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
-                false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
-        if (!builder.isEmpty()) {
-            return builder;
-        }
-        Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray())
-                + " fallbackLocale=" + fallbackLocale);
-        return builder;
-    }
-
     private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
             final ArrayList<InputMethodInfo> imis, final Context context,
             @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) {
@@ -353,21 +316,10 @@
     }
 
     public static ArrayList<InputMethodInfo> getDefaultEnabledImes(final Context context,
-            final boolean isSystemReady, final ArrayList<InputMethodInfo> imis) {
+            final ArrayList<InputMethodInfo> imis) {
         final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
-        if (!isSystemReady) {
-            // When the system is not ready, the system locale is not stable and reliable. Hence
-            // we will pick up IMEs that support software keyboard based on the fallback locale.
-            // Also pick up suitable IMEs regardless of the software keyboard support.
-            // (e.g. Voice IMEs)
-            return getMinimumKeyboardSetWithoutSystemLocale(imis, context, fallbackLocale)
-                    .fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
-                            true /* checkCountry */, SUBTYPE_MODE_ANY)
-                    .build();
-        }
-
-        // When the system is ready, we will primarily rely on the system locale, but also keep
-        // relying on the fallback locale as a last resort.
+        // We will primarily rely on the system locale, but also keep relying on the fallback locale
+        // as a last resort.
         // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs),
         // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic"
         // subtype)
diff --git a/core/java/com/android/internal/logging/legacy/CounterParser.java b/core/java/com/android/internal/logging/legacy/CounterParser.java
deleted file mode 100644
index f318503..0000000
--- a/core/java/com/android/internal/logging/legacy/CounterParser.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-/**
- * Parse the Android counter event logs.
- * @hide
- */
-public class CounterParser extends TagParser {
-    private static final String TAG = "CounterParser";
-    private static final int EVENTLOG_TAG = 524290;
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        if (operands.length >= 2) {
-            try {
-                String name = ((String) operands[0]);
-                int value = (Integer) operands[1];
-                logCount(logger, name, value);
-            } catch (ClassCastException e) {
-                if (debug) {
-                    Log.d(TAG, "unexpected operand type", e);
-                }
-            }
-        } else if (debug) {
-            Log.d(TAG, "wrong number of operands: " + operands.length);
-        }
-    }
-
-    protected void logCount(TronLogger logger, String name, int value) {
-        logger.incrementBy(TronCounters.TRON_AOSP_PREFIX + name, value);
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/EventLogCollector.java b/core/java/com/android/internal/logging/legacy/EventLogCollector.java
index eba7d0f..46e70ea 100644
--- a/core/java/com/android/internal/logging/legacy/EventLogCollector.java
+++ b/core/java/com/android/internal/logging/legacy/EventLogCollector.java
@@ -45,19 +45,6 @@
 
     private EventLogCollector() {
         mTagParsers = new ArrayMap<>();
-        addParser(new SysuiViewVisibilityParser());
-        addParser(new SysuiActionParser());
-        addParser(new SysuiQueryParser());
-        addParser(new NotificationPanelRevealedParser());
-        addParser(new NotificationPanelHiddenParser());
-        addParser(new NotificationClickedParser());
-        addParser(new NotificationActionClickedParser());
-        addParser(new NotificationCanceledParser());
-        addParser(new NotificationVisibilityParser());
-        addParser(new NotificationAlertParser());
-        addParser(new NotificationExpansionParser());
-        addParser(new CounterParser());
-        addParser(new HistogramParser());
         addParser(new LockscreenGestureParser());
         addParser(new StatusBarStateParser());
         addParser(new PowerScreenStateParser());
diff --git a/core/java/com/android/internal/logging/legacy/HistogramParser.java b/core/java/com/android/internal/logging/legacy/HistogramParser.java
deleted file mode 100644
index bb7e75c..0000000
--- a/core/java/com/android/internal/logging/legacy/HistogramParser.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-/**
- * Parse the Android histogram event logs.
- * @hide
- */
-public class HistogramParser extends CounterParser {
-    private static final String TAG = "HistogramParser";
-    private static final int EVENTLOG_TAG = 524291;
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    protected void logCount(TronLogger logger, String name, int value) {
-        logger.incrementIntHistogram("tron_varz_" + name, value);
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
deleted file mode 100644
index 79f3eb8..0000000
--- a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the Android notification action button interaction event logs.
- * @hide
- */
-public class NotificationActionClickedParser extends TagParser {
-    private static final String TAG = "NotificationAction";
-    private static final int EVENTLOG_TAG = 27521;
-
-    private final NotificationKey mKey;
-
-    public NotificationActionClickedParser() {
-        mKey = new NotificationKey();
-    }
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        if (operands.length > 1) {
-            try {
-                if (mKey.parse((String) operands[0])) {
-                    int index = (Integer) operands[1];
-                    parseTimes(operands, 2);
-                    LogMaker proto = logger.obtain();
-                    proto.setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION);
-                    proto.setType(MetricsEvent.TYPE_ACTION);
-                    proto.setSubtype(index);
-                    proto.setTimestamp(eventTimeMs);
-                    proto.setPackageName(mKey.mPackageName);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_ID, mKey.mId);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_TAG, mKey.mTag);
-                    filltimes(proto);
-                    logger.addEvent(proto);
-                } else if (debug) {
-                    Log.e(TAG, "unable to parse key.");
-                }
-            } catch (ClassCastException e) {
-                if (debug) {
-                    Log.e(TAG, "unexpected operand type: ", e);
-                }
-            }
-        } else if (debug) {
-            Log.w(TAG, "wrong number of operands: " + operands.length);
-        }
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java b/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
deleted file mode 100644
index 9548fb0..0000000
--- a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the new Android notification alert event logs.
- * @hide
- */
-public class NotificationAlertParser extends TagParser {
-    private static final String TAG = "NotificationAlertParser";
-    private static final int EVENTLOG_TAG = 27532;
-
-    @VisibleForTesting
-    static final int BUZZ = 0x00000001;
-    @VisibleForTesting
-    static final int BEEP = 0x00000002;
-    @VisibleForTesting
-    static final int BLINK = 0x00000004;
-
-    private final NotificationKey mKey;
-
-    public NotificationAlertParser() {
-        mKey = new NotificationKey();
-    }
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        if (operands.length > 3) {
-            try {
-                final String keyString = (String) operands[0];
-                final boolean buzz = ((Integer) operands[1]) == 1;
-                final boolean beep = ((Integer) operands[2]) == 1;
-                final boolean blink = ((Integer) operands[3]) == 1;
-
-                if (mKey.parse(keyString)) {
-                    LogMaker proto = logger.obtain();
-                    proto.setCategory(MetricsEvent.NOTIFICATION_ALERT);
-                    proto.setType(MetricsEvent.TYPE_OPEN);
-                    proto.setSubtype((buzz ? BUZZ : 0) | (beep ? BEEP : 0) | (blink ? BLINK : 0));
-                    proto.setTimestamp(eventTimeMs);
-                    proto.setPackageName(mKey.mPackageName);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_ID, mKey.mId);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_TAG, mKey.mTag);
-                    filltimes(proto);
-                    logger.addEvent(proto);
-                } else {
-                    if (debug) {
-                        Log.e(TAG, "unable to parse key: " + keyString);
-                    }
-                }
-            } catch (ClassCastException e) {
-                if (debug) {
-                    Log.e(TAG, "unexpected operand type: ", e);
-                }
-                return;
-            }
-        } else if (debug) {
-            Log.w(TAG, "wrong number of operands: " + operands.length);
-        }
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java b/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
deleted file mode 100644
index 80eb004..0000000
--- a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the Android notification cancellation event logs.
- * @hide
- */
-public class NotificationCanceledParser extends TagParser {
-    private static final String TAG = "NotificationCanceled";
-    private static final int EVENTLOG_TAG = 27530;
-
-    // from com.android.server.notification.NotificationManagerService
-    static final int REASON_DELEGATE_CLICK = 1;
-    static final int REASON_DELEGATE_CANCEL = 2;
-    static final int REASON_DELEGATE_CANCEL_ALL = 3;
-    static final int REASON_PACKAGE_BANNED = 7;
-    static final int REASON_LISTENER_CANCEL = 10;
-    static final int REASON_LISTENER_CANCEL_ALL = 11;
-
-    private final NotificationKey mKey;
-
-    public NotificationCanceledParser() {
-        mKey = new NotificationKey();
-    }
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        if (operands.length > 1) {
-            try {
-                final String keyString = (String) operands[0];
-                final int reason = (Integer) operands[1];
-                parseTimes(operands, 2);
-
-                // handle old style log
-                // TODO: delete once M is launched
-                if (operands.length < 5) {
-                    mSinceVisibleMillis = mSinceUpdateMillis;
-                    mSinceUpdateMillis = 0;
-                }
-
-                boolean intentional = true;
-                switch (reason) {
-                    case REASON_DELEGATE_CANCEL:
-                    case REASON_DELEGATE_CANCEL_ALL:
-                    case REASON_LISTENER_CANCEL:
-                    case REASON_LISTENER_CANCEL_ALL:
-                    case REASON_DELEGATE_CLICK:
-                    case REASON_PACKAGE_BANNED:
-                        break;
-                    default:
-                        intentional = false;
-                }
-
-                if (mKey.parse(keyString)) {
-                    if (intentional) {
-                        LogMaker proto = logger.obtain();
-                        proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
-                        proto.setType(MetricsEvent.TYPE_DISMISS);
-                        proto.setSubtype(reason);
-                        proto.setTimestamp(eventTimeMs);
-                        proto.setPackageName(mKey.mPackageName);
-                        proto.addTaggedData(MetricsEvent.NOTIFICATION_ID, mKey.mId);
-                        proto.addTaggedData(MetricsEvent.NOTIFICATION_TAG, mKey.mTag);
-                        filltimes(proto);
-                        logger.addEvent(proto);
-                    }
-                } else if (debug) {
-                    Log.e(TAG, "unable to parse key: " + keyString);
-                }
-            } catch (ClassCastException e) {
-                if (debug) {
-                    Log.e(TAG, "unexpected operand type: ", e);
-                }
-            }
-        }
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
deleted file mode 100644
index eee4701..0000000
--- a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the Android notification interaction event logs.
- * @hide
- */
-public class NotificationClickedParser extends TagParser {
-    private static final String TAG = "NotificationClicked";
-    private static final int EVENTLOG_TAG = 27520;
-
-    private final NotificationKey mKey;
-
-    public NotificationClickedParser() {
-        mKey = new NotificationKey();
-    }
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        if (operands.length > 0) {
-            try {
-                if (mKey.parse((String) operands[0])) {
-                    parseTimes(operands, 1);
-                    LogMaker proto = logger.obtain();
-                    proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
-                    proto.setType(MetricsEvent.TYPE_ACTION);
-                    proto.setTimestamp(eventTimeMs);
-                    proto.setPackageName(mKey.mPackageName);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_ID, mKey.mId);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_TAG, mKey.mTag);
-                    filltimes(proto);
-                    logger.addEvent(proto);
-                } else if (debug) {
-                    Log.e(TAG, "unable to parse key.");
-                }
-            } catch (ClassCastException e) {
-                if (debug) {
-                    Log.e(TAG, "unexpected operand type: ", e);
-                }
-            }
-        } else if (debug) {
-            Log.w(TAG, "wrong number of operands: " + operands.length);
-        }
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java b/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
deleted file mode 100644
index 84cd999..0000000
--- a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the Android notification expansion event logs.
- * @hide
- */
-public class NotificationExpansionParser extends TagParser {
-    private static final String TAG = "NotificationExpansion";
-    private static final int EVENTLOG_TAG = 27511;
-
-    private final NotificationKey mKey;
-
-    public NotificationExpansionParser() {
-        mKey = new NotificationKey();
-    }
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        if (operands.length > 2) {
-            try {
-                if (mKey.parse((String) operands[0])) {
-                    boolean byUser = ((Integer) operands[1]) == 1;
-                    boolean expanded = ((Integer) operands[2]) == 1;
-                    parseTimes(operands, 3);
-
-                    if (!byUser || !expanded) {
-                        return;
-                    }
-                    LogMaker proto = logger.obtain();
-                    proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
-                    proto.setType(MetricsEvent.TYPE_DETAIL);
-                    proto.setTimestamp(eventTimeMs);
-                    proto.setPackageName(mKey.mPackageName);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_ID, mKey.mId);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_TAG, mKey.mTag);
-                    filltimes(proto);
-                    logger.addEvent(proto);
-                } else if (debug) {
-                    Log.e(TAG, "unable to parse key.");
-                }
-            } catch (ClassCastException e) {
-                if (debug) {
-                    Log.e(TAG, "unexpected operand type: ", e);
-                }
-            }
-        } else if (debug) {
-            Log.w(TAG, "wrong number of operands: " + operands.length);
-        }
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/NotificationKey.java b/core/java/com/android/internal/logging/legacy/NotificationKey.java
deleted file mode 100644
index f8cac34..0000000
--- a/core/java/com/android/internal/logging/legacy/NotificationKey.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-/**
- * Parse Android notification keys
- * @hide
- */
-public class NotificationKey {
-
-    private static final String TAG = "NotificationKey";
-
-    public int mUser;
-    public String mPackageName;
-    public int mId;
-    public String mTag;
-    public int mUid;
-
-    public boolean parse(String key) {
-        if (key == null) {
-            return false;
-        }
-        boolean debug = Util.debug();
-        String[] parts = key.split("\\|");
-        if (parts.length == 5) {
-            try {
-                mUser = Integer.valueOf(parts[0]);
-                mPackageName = parts[1];
-                mId = Integer.valueOf(parts[2]);
-                mTag = parts[3].equals("null") ? "" : parts[3];
-                mUid = Integer.valueOf(parts[4]);
-                return true;
-            } catch (NumberFormatException e) {
-                if (debug) {
-                    Log.w(TAG, "could not parse notification key.", e);
-                }
-                return false;
-            }
-        }
-        if (debug) {
-            Log.w(TAG, "wrong number of parts in notification key: " + key);
-        }
-        return false;
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
deleted file mode 100644
index a064a2e..0000000
--- a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the Android notification panel visibility event logs.
- * @hide
- */
-public class NotificationPanelHiddenParser extends TagParser {
-    private static final String TAG = "NotificationPanelHidden";
-    private static final int EVENTLOG_TAG = 27501;
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        LogMaker proto = logger.obtain();
-        proto.setCategory(MetricsEvent.NOTIFICATION_PANEL);
-        proto.setType(MetricsEvent.TYPE_CLOSE);
-        proto.setTimestamp(eventTimeMs);
-        logger.addEvent(proto);
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
deleted file mode 100644
index 4d19564..0000000
--- a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the Android notification panel visibility event logs.
- * @hide
- */
-public class NotificationPanelRevealedParser extends TagParser {
-    private static final String TAG = "NotificationPanelRevea";
-    private static final int EVENTLOG_TAG = 27500;
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        if (operands.length >= 1) {
-            try {
-                int load = ((Integer) operands[0]).intValue();
-                //logger.incrementBy(TronCounters.TRON_NOTIFICATION_LOAD, load);
-            } catch (ClassCastException e) {
-                if (debug) {
-                    Log.e(TAG, "unexpected operand type: ", e);
-                }
-            }
-        }
-
-        LogMaker proto = logger.obtain();
-        proto.setCategory(MetricsEvent.NOTIFICATION_PANEL);
-        proto.setType(MetricsEvent.TYPE_OPEN);
-        proto.setTimestamp(eventTimeMs);
-        logger.addEvent(proto);
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java b/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
deleted file mode 100644
index 2d2cd909..0000000
--- a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the new Android notification visibility event logs.
- * @hide
- */
-public class NotificationVisibilityParser extends TagParser {
-    private static final String TAG = "NotificationVisibility";
-    private static final int EVENTLOG_TAG = 27531;
-
-    private final NotificationKey mKey;
-
-    public NotificationVisibilityParser() {
-        mKey = new NotificationKey();
-    }
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        if (operands.length > 1) {
-            try {
-                final String keyString = (String) operands[0];
-                final boolean visible = ((Integer) operands[1]) == 1;
-                parseTimes(operands, 2);
-                int index = 0;
-                if (operands.length > 5 && operands[5] instanceof Integer) {
-                    index = (Integer) operands[5];
-                }
-
-                if (mKey.parse(keyString)) {
-                    LogMaker proto = logger.obtain();
-                    proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
-                    proto.setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
-                    proto.setTimestamp(eventTimeMs);
-                    proto.setPackageName(mKey.mPackageName);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_ID, mKey.mId);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_TAG, mKey.mTag);
-                    proto.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, index);
-                    filltimes(proto);
-                    logger.addEvent(proto);
-                } else {
-                    if (debug) {
-                        Log.e(TAG, "unable to parse key: " + keyString);
-                    }
-                }
-            } catch (ClassCastException e) {
-                if (debug) {
-                    Log.e(TAG, "unexpected operand type: ", e);
-                }
-                return;
-            }
-        } else if (debug) {
-            Log.w(TAG, "wrong number of operands: " + operands.length);
-        }
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
deleted file mode 100644
index 1148ee5..0000000
--- a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the Android framework sysui action logs.
- * @hide
- */
-public class SysuiActionParser extends TagParser {
-    private static final String TAG = "SysuiActionParser";
-    private static final int EVENTLOG_TAG = 524288;
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        try {
-            String packageName = null;
-            int subType = -1;
-            boolean hasSubType = false;
-            if (operands.length > 1) {
-                String arg = (String) operands[1];
-                if (arg.equals("true")) {
-                    hasSubType = true;
-                    subType = 1;
-                } else if (arg.equals("false")) {
-                    hasSubType = true;
-                    subType = 0;
-                } else if (arg.matches("^-?\\d+$")) {
-                    try {
-                        subType = Integer.valueOf(arg);
-                        hasSubType = true;
-                    } catch (NumberFormatException e) {
-                    }
-                } else {
-                    packageName = arg;
-                }
-            }
-            if (operands.length > 0) {
-                int category = ((Integer) operands[0]).intValue();
-                LogMaker proto = logger.obtain();
-                proto.setCategory(category);
-                proto.setType(MetricsEvent.TYPE_ACTION);
-                proto.setTimestamp(eventTimeMs);
-                if (packageName != null) {
-                    proto.setPackageName(packageName);
-                }
-                if (hasSubType) {
-                    proto.setSubtype(subType);
-                }
-                logger.addEvent(proto);
-            }
-        } catch (ClassCastException e) {
-            if (debug) {
-                Log.e(TAG, "unexpected operand type: ", e);
-            }
-        }
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/SysuiQueryParser.java b/core/java/com/android/internal/logging/legacy/SysuiQueryParser.java
deleted file mode 100644
index 7b3c0a7..0000000
--- a/core/java/com/android/internal/logging/legacy/SysuiQueryParser.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-/**
- * Parse the Android framework sysui search query logs.
- * For now just treat them like actions.
- * @hide
- */
-public class SysuiQueryParser extends SysuiActionParser {
-    private static final String TAG = "SysuiQueryParser";
-
-    private static final int EVENTLOG_TAG = 524289;
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-}
diff --git a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java b/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
deleted file mode 100644
index 1223b8d..0000000
--- a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.util.Log;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-/**
- * Parse the Android framework sysui view visibility logs.
- * @hide
- */
-public class SysuiViewVisibilityParser extends TagParser {
-    private static final String TAG = "SysuiViewVisibility";
-    private static final int EVENTLOG_TAG = 524287;
-
-    @Override
-    public int getTag() {
-        return EVENTLOG_TAG;
-    }
-
-    @Override
-    public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        final boolean debug = Util.debug();
-        if (operands.length >= 2) {
-            try {
-                int category = ((Integer) operands[0]).intValue();
-                boolean visibility = ((Integer) operands[1]).intValue() != 0;
-
-                LogMaker proto = logger.obtain();
-                proto.setCategory(category);
-                proto.setType(visibility ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
-                proto.setTimestamp(eventTimeMs);
-                logger.addEvent(proto);
-            } catch (ClassCastException e) {
-                if (debug) {
-                    Log.e(TAG, "unexpected operand type: ", e);
-                }
-            }
-        } else if (debug) {
-            Log.w(TAG, "wrong number of operands: " + operands.length);
-        }
-    }
-}
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index cf14471..11e7102 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -53,6 +53,11 @@
      */
     private static final int SNAP_ONLY_1_1 = 2;
 
+    /**
+     * 1 snap target: minimized height, (1 - minimized height)
+     */
+    private static final int SNAP_MODE_MINIMIZED = 3;
+
     private final float mMinFlingVelocityPxPerSecond;
     private final float mMinDismissVelocityPxPerSecond;
     private final int mDisplayWidth;
@@ -62,6 +67,7 @@
     private final Rect mInsets = new Rect();
     private final int mSnapMode;
     private final int mMinimalSizeResizableTask;
+    private final int mTaskHeightInMinimizedMode;
     private final float mFixedRatio;
     private boolean mIsHorizontalDivision;
 
@@ -93,6 +99,11 @@
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
             boolean isHorizontalDivision, Rect insets) {
+        this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, false);
+    }
+
+    public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+            boolean isHorizontalDivision, Rect insets, boolean isMinimizedMode) {
         mMinFlingVelocityPxPerSecond =
                 MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
         mMinDismissVelocityPxPerSecond =
@@ -102,12 +113,14 @@
         mDisplayHeight = displayHeight;
         mIsHorizontalDivision = isHorizontalDivision;
         mInsets.set(insets);
-        mSnapMode = res.getInteger(
-                com.android.internal.R.integer.config_dockedStackDividerSnapMode);
+        mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
+                res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
         mFixedRatio = res.getFraction(
                 com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
         mMinimalSizeResizableTask = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.default_minimal_size_resizable_task);
+        mTaskHeightInMinimizedMode = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.task_height_of_minimized_mode);
         calculateTargets(isHorizontalDivision);
         mFirstSplitTarget = mTargets.get(1);
         mLastSplitTarget = mTargets.get(mTargets.size() - 2);
@@ -246,6 +259,7 @@
         int dividerMax = isHorizontalDivision
                 ? mDisplayHeight
                 : mDisplayWidth;
+        int navBarSize = isHorizontalDivision ? mInsets.bottom : mInsets.right;
         mTargets.add(new SnapTarget(-mDividerSize, -mDividerSize, SnapTarget.FLAG_DISMISS_START,
                 0.35f));
         switch (mSnapMode) {
@@ -258,8 +272,10 @@
             case SNAP_ONLY_1_1:
                 addMiddleTarget(isHorizontalDivision);
                 break;
+            case SNAP_MODE_MINIMIZED:
+                addMinimizedTarget(isHorizontalDivision);
+                break;
         }
-        int navBarSize = isHorizontalDivision ? mInsets.bottom : mInsets.right;
         mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
                 SnapTarget.FLAG_DISMISS_END, 0.35f));
     }
@@ -315,6 +331,12 @@
         mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
     }
 
+    private void addMinimizedTarget(boolean isHorizontalDivision) {
+        int position = mTaskHeightInMinimizedMode;
+        position += isHorizontalDivision ? mInsets.top : mInsets.left;
+        mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+    }
+
     public SnapTarget getMiddleTarget() {
         return mMiddleTarget;
     }
diff --git a/core/jni/android_hardware_Radio.cpp b/core/jni/android_hardware_Radio.cpp
index 397e67b..b6b1ac7 100644
--- a/core/jni/android_hardware_Radio.cpp
+++ b/core/jni/android_hardware_Radio.cpp
@@ -23,7 +23,7 @@
 #include "JNIHelp.h"
 #include "core_jni_helpers.h"
 #include <system/radio.h>
-#include <system/radio_metadata.h>
+#include <system/RadioMetadataWrapper.h>
 #include <radio/RadioCallback.h>
 #include <radio/Radio.h>
 #include <utils/RefBase.h>
@@ -752,7 +752,7 @@
     }
 
     struct radio_program_info nInfo;
-    radio_metadata_allocate(&nInfo.metadata, 0, 0);
+    RadioMetadataWrapper metadataWrapper(&nInfo.metadata);
     jobject jInfo = NULL;
     int jStatus;
 
@@ -770,7 +770,6 @@
     if (jInfo != NULL) {
         env->DeleteLocalRef(jInfo);
     }
-    radio_metadata_deallocate(nInfo.metadata);
     return jStatus;
 }
 
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index d382f24..723dce6 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -681,7 +681,7 @@
                                                           jint smallestScreenWidthDp,
                                                           jint screenWidthDp, jint screenHeightDp,
                                                           jint screenLayout, jint uiMode,
-                                                          jint sdkVersion)
+                                                          jint colorMode, jint sdkVersion)
 {
     AssetManager* am = assetManagerForJavaObject(env, clazz);
     if (am == NULL) {
@@ -712,6 +712,7 @@
     config.screenHeightDp = (uint16_t)screenHeightDp;
     config.screenLayout = (uint8_t)screenLayout;
     config.uiMode = (uint8_t)uiMode;
+    config.colorMode = (uint8_t)colorMode;
     config.sdkVersion = (uint16_t)sdkVersion;
     config.minorVersion = 0;
 
@@ -1691,7 +1692,7 @@
     { "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
         (void*) android_content_AssetManager_getSizeConfigurations },
     // @FastNative
-    { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIII)V",
+    { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIIII)V",
         (void*) android_content_AssetManager_setConfiguration },
     // @FastNative
     { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a3fef27..ab3e311 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -98,11 +98,12 @@
 // ----------------------------------------------------------------------------
 
 static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
-        jstring nameStr, jint w, jint h, jint format, jint flags) {
+        jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject) {
     ScopedUtfChars name(env, nameStr);
     sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
+    SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
     sp<SurfaceControl> surface = client->createSurface(
-            String8(name.c_str()), w, h, format, flags);
+            String8(name.c_str()), w, h, format, flags, parent);
     if (surface == NULL) {
         jniThrowException(env, OutOfResourcesException, NULL);
         return 0;
@@ -147,8 +148,8 @@
     }
     Rect sourceCrop = rectFromObj(env, sourceCropObj);
     if (allLayers) {
-        minLayer = 0;
-        maxLayer = -1;
+        minLayer = INT32_MIN;
+        maxLayer = INT32_MAX;
     }
     sp<GraphicBuffer> buffer;
     status_t res = ScreenshotClient::captureToBuffer(displayToken,
@@ -181,8 +182,8 @@
     std::unique_ptr<ScreenshotClient> screenshot(new ScreenshotClient());
     status_t res;
     if (allLayers) {
-        minLayer = 0;
-        maxLayer = -1;
+        minLayer = INT32_MIN;
+        maxLayer = INT32_MAX;
     }
 
     res = screenshot->update(displayToken, sourceCrop, width, height,
@@ -254,8 +255,8 @@
             Rect sourceCrop(left, top, right, bottom);
 
             if (allLayers) {
-                minLayer = 0;
-                maxLayer = -1;
+                minLayer = INT32_MIN;
+                maxLayer = INT32_MAX;
             }
             ScreenshotClient::capture(displayToken,
                     consumer->getIGraphicBufferProducer(), sourceCrop,
@@ -741,7 +742,7 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sSurfaceControlMethods[] = {
-    {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIII)J",
+    {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJ)J",
             (void*)nativeCreate },
     {"nativeRelease", "(J)V",
             (void*)nativeRelease },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2b6c0ba..540f924 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -82,6 +82,10 @@
     <protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
     <protected-broadcast android:name="android.intent.action.USER_INITIALIZE" />
     <protected-broadcast android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION" />
+    <protected-broadcast android:name="android.intent.action.OVERLAY_ADDED" />
+    <protected-broadcast android:name="android.intent.action.OVERLAY_CHANGED" />
+    <protected-broadcast android:name="android.intent.action.OVERLAY_REMOVED" />
+    <protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" />
 
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
@@ -2107,6 +2111,12 @@
         android:protectionLevel="signature" />
     <uses-permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"/>
 
+    <!-- Allows an application to enable, disable and change priority of
+         runtime resource overlays.
+         @hide -->
+    <permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"
+        android:protectionLevel="signature|privileged|development" />
+
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
     <!-- ========================================= -->
diff --git a/libs/androidfw/tests/data/lib/AndroidManifest.xml b/core/res/res/color/text_color_secondary.xml
similarity index 61%
copy from libs/androidfw/tests/data/lib/AndroidManifest.xml
copy to core/res/res/color/text_color_secondary.xml
index 02f5d3e..60e0af8 100644
--- a/libs/androidfw/tests/data/lib/AndroidManifest.xml
+++ b/core/res/res/color/text_color_secondary.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
@@ -14,7 +14,10 @@
      limitations under the License.
 -->
 
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.lib">
-    <application />
-</manifest>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="?attr/disabledAlpha"
+        android:color="?attr/colorForeground"/>
+    <item android:alpha="?attr/secondaryContentAlpha"
+        android:color="?attr/colorForeground"/>
+</selector>
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
new file mode 100644
index 0000000..d55a012
--- /dev/null
+++ b/core/res/res/layout/autofill_save.xml
@@ -0,0 +1,57 @@
+<?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.
+-->
+
+<!-- TODO(b/33197203) remove hardcoded color once color is final -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="16dip"
+    android:paddingEnd="16dip"
+    android:paddingTop="16dip"
+    android:paddingBottom="16dip"
+    android:background="#FDF8C8">
+
+  <!-- TODO(b/33197203) use.R.string once final wording is done -->
+  <TextView
+      android:id="@+id/autofill_save_title"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="Save for autofill?"
+      android:singleLine="true"/>
+
+  <TextView
+      android:id="@+id/autofill_save_no"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_below="@+id/autofill_save_title"
+      android:layout_toLeftOf="@+id/autofill_save_yes"
+      android:layout_marginRight="16dip"
+      android:text="No thanks"
+      android:textAllCaps="true"
+      android:singleLine="true"/>
+
+    <TextView
+      android:id="@+id/autofill_save_yes"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_below="@+id/autofill_save_title"
+      android:layout_alignParentRight="true"
+      android:text="Save"
+      android:textAllCaps="true"
+      android:singleLine="true"/>
+
+</RelativeLayout>
diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index 1ae317c..e2c68b5 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -52,19 +52,18 @@
                 android:ellipsize="marquee"
                 android:fadingEdge="horizontal"
                 android:textSize="20sp"
-                android:textColor="@android:color/white"
+                android:textColor="#e6fafafa"
             />
             <TextView android:id="@+id/text"
                 android:layout_width="match_parent"
                 android:layout_height="0dp"
-                android:paddingBottom="@dimen/notification_content_margin_bottom"
                 android:textAppearance="@style/TextAppearance.Material.Notification"
                 android:singleLine="false"
                 android:layout_weight="1"
                 android:gravity="top"
                 android:visibility="gone"
-                android:textSize="18sp"
-                android:textColor="@android:color/white"
+                android:textSize="16sp"
+                android:textColor="#ccfafafa"
                 android:layout_marginTop="4dp"
             />
         </LinearLayout>
diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml
index 72588c7..0c462fd 100644
--- a/core/res/res/layout/search_view.xml
+++ b/core/res/res/layout/search_view.xml
@@ -43,7 +43,8 @@
         android:layout_height="match_parent"
         android:layout_gravity="center_vertical"
         android:focusable="true"
-        android:contentDescription="@string/searchview_description_search" />
+        android:contentDescription="@string/searchview_description_search"
+        android:tooltipText="@string/searchview_description_search" />
 
     <LinearLayout
         android:id="@+id/search_edit_frame"
diff --git a/core/res/res/values-mcc334-mnc050/config.xml b/core/res/res/values-mcc334-mnc050/config.xml
index f6777d0..616a8e8 100644
--- a/core/res/res/values-mcc334-mnc050/config.xml
+++ b/core/res/res/values-mcc334-mnc050/config.xml
@@ -40,4 +40,8 @@
       <item>Modem,modem.iusacellgsm.mx,,,iusacellgsm,iusacellgsm,,,,,334,050,1,DUN</item>
     </string-array>
 
+    <!-- Do not translate. Defines the slots is Two Digit Number for dialing normally not USSD -->
+    <string-array translatable="false" name="config_twoDigitNumberPattern">
+        <item>"#9"</item>
+    </string-array>
 </resources>
diff --git a/core/res/res/values-mcc334-mnc090/config.xml b/core/res/res/values-mcc334-mnc090/config.xml
new file mode 100644
index 0000000..1632a42
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc090/config.xml
@@ -0,0 +1,28 @@
+<?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 my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Do not translate. Defines the slots is Two Digit Number for dialing normally not USSD -->
+
+    <string-array translatable="false" name="config_twoDigitNumberPattern">
+        <item>"#9"</item>
+    </string-array>
+</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 11a96f7..34f78f3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -64,6 +64,8 @@
         <attr name="disabledAlpha" format="float" />
         <!-- The alpha applied to the foreground color to create the primary text color. -->
         <attr name="primaryContentAlpha" format="float" />
+        <!-- The alpha applied to the foreground color to create the secondary text color. -->
+        <attr name="secondaryContentAlpha" format="float" />
         <!-- Default background dim amount when a menu, dialog, or something similar pops up. -->
         <attr name="backgroundDimAmount" format="float" />
         <!-- Control whether dimming behind the window is enabled.  The default
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index db89c22..0a24565 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -78,6 +78,8 @@
     <item name="disabled_alpha_material_dark" format="float" type="dimen">0.30</item>
     <item name="primary_content_alpha_material_light" format="float" type="dimen">1</item>
     <item name="primary_content_alpha_material_dark" format="float" type="dimen">0.87</item>
+    <item name="secondary_content_alpha_material_light" format="float" type="dimen">.7</item>
+    <item name="secondary_content_alpha_material_dark" format="float" type="dimen">0.54</item>
 
     <item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item>
     <item name="highlight_alpha_material_dark" format="float" type="dimen">0.20</item>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6a8b556..9873762 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2278,6 +2278,13 @@
     <!-- Whether to use voip audio mode for ims call -->
     <bool name="config_use_voip_mode_for_ims">false</bool>
 
+    <!-- ImsService package name to bind to by default. If none is specified in an overlay, an
+         empty string is passed in -->
+    <string name="config_ims_package"/>
+
+    <!-- Flag specifying whether or not IMS will use the dynamic ImsResolver -->
+    <bool name="config_dynamic_bind_ims">true</bool>
+
     <bool name="config_networkSamplingWakesDevice">true</bool>
 
     <string-array translatable="false" name="config_cdma_home_system" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e6358a3..4266f88 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -488,6 +488,9 @@
     <!-- The default minimal size of a resizable task, in both dimensions. -->
     <dimen name="default_minimal_size_resizable_task">220dp</dimen>
 
+    <!-- Height of a task when in minimized mode from the top when launcher is resizable. -->
+    <dimen name="task_height_of_minimized_mode">80dp</dimen>
+
     <!-- Minimum "smallest width" of the display for cascading menus to be enabled. -->
     <dimen name="cascading_menus_min_smallest_width">720dp</dimen>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1146871..f981029 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2800,6 +2800,7 @@
     </public-group>
 
     <public type="attr" name="primaryContentAlpha" />
+    <public type="attr" name="secondaryContentAlpha" />
 
   <!-- ===============================================================
        DO NOT ADD UN-GROUPED ITEMS HERE
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a732998..554e123 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -254,6 +254,8 @@
   <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
+  <java-symbol type="string" name="config_ims_package" />
+  <java-symbol type="bool" name="config_dynamic_bind_ims" />
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
   <java-symbol type="bool" name="config_sip_wifi_only" />
@@ -504,7 +506,6 @@
   <java-symbol type="string" name="NetworkPreferenceSwitchTitle" />
   <java-symbol type="string" name="SetupCallDefault" />
   <java-symbol type="string" name="accept" />
-  <java-symbol type="string" name="accessibility_enabled" />
   <java-symbol type="string" name="activity_chooser_view_see_all" />
   <java-symbol type="string" name="activitychooserview_choose_application" />
   <java-symbol type="string" name="activitychooserview_choose_application_error" />
@@ -601,7 +602,6 @@
   <java-symbol type="string" name="contentServiceSync" />
   <java-symbol type="string" name="contentServiceSyncNotificationTitle" />
   <java-symbol type="string" name="contentServiceTooManyDeletesNotificationDesc" />
-  <java-symbol type="string" name="continue_to_enable_accessibility" />
   <java-symbol type="string" name="date_and_time" />
   <java-symbol type="string" name="date_picker_decrement_day_button" />
   <java-symbol type="string" name="date_picker_decrement_month_button" />
@@ -648,7 +648,6 @@
   <java-symbol type="string" name="widget_default_class_name" />
   <java-symbol type="string" name="emergency_calls_only" />
   <java-symbol type="array" name="config_ephemeralResolverPackage" />
-  <java-symbol type="string" name="enable_accessibility_canceled" />
   <java-symbol type="string" name="eventTypeAnniversary" />
   <java-symbol type="string" name="eventTypeBirthday" />
   <java-symbol type="string" name="eventTypeCustom" />
@@ -1777,6 +1776,7 @@
   <java-symbol type="id" name="replace_message" />
   <java-symbol type="fraction" name="config_dimBehindFadeDuration" />
   <java-symbol type="dimen" name="default_minimal_size_resizable_task" />
+  <java-symbol type="dimen" name="task_height_of_minimized_mode" />
   <java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" />
   <java-symbol type="fraction" name="config_autoBrightnessAdjustmentMaxGamma" />
   <java-symbol type="integer" name="config_autoBrightnessAmbientLightHorizon"/>
@@ -2814,8 +2814,6 @@
 
   <java-symbol type="string" name="config_icon_mask" />
 
-  <java-symbol type="attr" name="primaryContentAlpha" />
-
   <!-- Accessibility Shortcut -->
   <java-symbol type="string" name="accessibility_shortcut_warning_dialog_title" />
   <java-symbol type="string" name="accessibility_shortcut_toogle_warning" />
@@ -2830,4 +2828,10 @@
   <java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/>
   <java-symbol type="dimen" name="item_touch_helper_swipe_escape_velocity"/>
   <java-symbol type="dimen" name="item_touch_helper_swipe_escape_max_velocity"/>
+
+  <!-- com.android.server.autofill -->
+  <java-symbol type="layout" name="autofill_save"/>
+  <java-symbol type="id" name="autofill_save_title" />
+  <java-symbol type="id" name="autofill_save_no" />
+  <java-symbol type="id" name="autofill_save_yes" />
 </resources>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 3587fec..b063baf 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -49,6 +49,7 @@
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
         <item name="disabledAlpha">@dimen/disabled_alpha_material_dark</item>
         <item name="primaryContentAlpha">@dimen/primary_content_alpha_material_dark</item>
+        <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_material_dark</item>
         <item name="backgroundDimAmount">0.6</item>
 
         <!-- Text styles -->
@@ -59,7 +60,7 @@
         <item name="textColorPrimaryInverse">@color/primary_text_material_light</item>
         <item name="textColorPrimaryActivated">@color/primary_text_inverse_when_activated_material</item>
         <item name="textColorPrimaryDisableOnly">@color/primary_text_disable_only_material_dark</item>
-        <item name="textColorSecondary">@color/secondary_text_material_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary</item>
         <item name="textColorSecondaryInverse">@color/secondary_text_material_light</item>
         <item name="textColorSecondaryActivated">@color/secondary_text_inverse_when_activated_material</item>
         <item name="textColorTertiary">@color/secondary_text_material_dark</item>
@@ -415,6 +416,7 @@
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
         <item name="disabledAlpha">@dimen/disabled_alpha_material_light</item>
         <item name="primaryContentAlpha">@dimen/primary_content_alpha_material_light</item>
+        <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_material_light</item>
         <item name="backgroundDimAmount">0.6</item>
 
         <!-- Text styles -->
@@ -424,7 +426,7 @@
         <item name="textColorPrimary">@color/text_color_primary</item>
         <item name="textColorPrimaryInverse">@color/primary_text_material_dark</item>
         <item name="textColorPrimaryActivated">@color/primary_text_inverse_when_activated_material</item>
-        <item name="textColorSecondary">@color/secondary_text_material_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary</item>
         <item name="textColorSecondaryInverse">@color/secondary_text_material_dark</item>
         <item name="textColorSecondaryActivated">@color/secondary_text_inverse_when_activated_material</item>
         <item name="textColorTertiary">@color/secondary_text_material_light</item>
@@ -809,7 +811,7 @@
 
         <item name="textColorPrimary">@color/text_color_primary</item>
         <item name="textColorPrimaryInverse">@color/primary_text_material_dark</item>
-        <item name="textColorSecondary">@color/secondary_text_material_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary</item>
         <item name="textColorSecondaryInverse">@color/secondary_text_material_dark</item>
         <item name="textColorTertiary">@color/secondary_text_material_light</item>
         <item name="textColorTertiaryInverse">@color/secondary_text_material_dark</item>
@@ -844,7 +846,7 @@
         <item name="textColorPrimary">@color/text_color_primary</item>
         <item name="textColorPrimaryInverse">@color/primary_text_material_light</item>
         <item name="textColorPrimaryDisableOnly">@color/primary_text_disable_only_material_dark</item>
-        <item name="textColorSecondary">@color/secondary_text_material_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary</item>
         <item name="textColorSecondaryInverse">@color/secondary_text_material_light</item>
         <item name="textColorTertiary">@color/secondary_text_material_dark</item>
         <item name="textColorTertiaryInverse">@color/secondary_text_material_light</item>
diff --git a/core/tests/coretests/src/android/metrics/LogMakerTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
index 35d8d93..b9c973f 100644
--- a/core/tests/coretests/src/android/metrics/LogMakerTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -115,6 +115,13 @@
         assertEquals(10, out[1]);
     }
 
+    public void testClearData() {
+        LogMaker builder = new LogMaker(0);
+        builder.addTaggedData(1, "onetwothree");
+        builder.clearTaggedData(1);
+        assertEquals(null, builder.getTaggedData(1));
+    }
+
     public void testGiantLogOmitted() {
         LogMaker badBuilder = new LogMaker(0);
         StringBuilder b = new StringBuilder();
@@ -125,4 +132,78 @@
         assertTrue(badBuilder.serialize().length < LogMaker.MAX_SERIALIZED_SIZE);
     }
 
+    public void testIdentityEquality() {
+        LogMaker a = new LogMaker(0);
+        a.addTaggedData(1, "onetwothree");
+        a.addTaggedData(2, 123);
+        a.addTaggedData(3, 123L);
+
+        assertTrue("objects should be equal to themselves", a.isSubsetOf(a));
+    }
+
+    public void testExactEquality() {
+        LogMaker a = new LogMaker(0);
+        a.addTaggedData(1, "onetwothree");
+        a.addTaggedData(2, 123);
+        a.addTaggedData(3, 123L);
+        LogMaker b = new LogMaker(0);
+        b.addTaggedData(1, "onetwothree");
+        b.addTaggedData(2, 123);
+        b.addTaggedData(3, 123L);
+
+        assertTrue("deep equality should be true", a.isSubsetOf(b));
+        assertTrue("deep equality shoudl be true", b.isSubsetOf(a));
+    }
+
+    public void testSubsetEquality() {
+        LogMaker a = new LogMaker(0);
+        a.addTaggedData(1, "onetwothree");
+        a.addTaggedData(2, 123);
+        LogMaker b = new LogMaker(0);
+        b.addTaggedData(1, "onetwothree");
+        b.addTaggedData(2, 123);
+        b.addTaggedData(3, 123L);
+
+        assertTrue("a is a strict subset of b", a.isSubsetOf(b));
+        assertTrue("b is not a strict subset of a", !b.isSubsetOf(a));
+    }
+
+    public void testInequality() {
+        LogMaker a = new LogMaker(0);
+        a.addTaggedData(1, "onetwofour");
+        a.addTaggedData(2, 1234);
+        LogMaker b = new LogMaker(0);
+        b.addTaggedData(1, "onetwothree");
+        b.addTaggedData(2, 123);
+        b.addTaggedData(3, 123L);
+
+        assertTrue("a is not a subset of b", !a.isSubsetOf(b));
+        assertTrue("b is not a subset of a", !b.isSubsetOf(a));
+    }
+
+    public void testWildcardEquality() {
+        LogMaker empty = new LogMaker(0);
+        empty.clearTaggedData(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY);  //dirty trick
+        LogMaker b = new LogMaker(0);
+        b.addTaggedData(1, "onetwothree");
+        b.addTaggedData(2, 123);
+        b.addTaggedData(3, 123L);
+
+        assertTrue("empty builder is a subset of anything", empty.isSubsetOf(b));
+    }
+
+    public void testNullEquality() {
+        LogMaker a = new LogMaker(0);
+        a.addTaggedData(1, "onetwofour");
+        a.addTaggedData(2, 1234);
+
+        assertTrue("a is not a subset of null", !a.isSubsetOf(null));
+    }
+
+    public void testMajorCategory() {
+        LogMaker a = new LogMaker(1);
+        LogMaker b = new LogMaker(2);
+        assertFalse(a.isSubsetOf(b));
+        assertFalse(b.isSubsetOf(a));
+    }
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsTest.java b/core/tests/coretests/src/android/provider/SettingsTest.java
new file mode 100644
index 0000000..0c9c3c17
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/SettingsTest.java
@@ -0,0 +1,541 @@
+/*
+ * 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.provider;
+
+import static com.google.android.collect.Sets.newHashSet;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static java.lang.reflect.Modifier.isFinal;
+import static java.lang.reflect.Modifier.isPublic;
+import static java.lang.reflect.Modifier.isStatic;
+
+import android.annotation.TargetApi;
+
+import android.support.test.runner.AndroidJUnit4;
+
+import java.lang.reflect.Field;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for Settings. */
+@TargetApi(26)
+@RunWith(AndroidJUnit4.class)
+public class SettingsTest {
+
+    /**
+     * The following blacklists contain settings that should *not* be backed up and restored to
+     * another device.  As a general rule, anything that is not user configurable should be
+     * blacklisted (and conversely, things that *are* user configurable *should* be backed up)
+     */
+    private static final Set<String> BACKUP_BLACKLISTED_SYSTEM_SETTINGS =
+            newHashSet(
+                    Settings.System.ADVANCED_SETTINGS, // candidate for backup?
+                    Settings.System.ALARM_ALERT, // backup candidate?
+                    Settings.System.ALARM_ALERT_CACHE, // internal cache
+                    Settings.System.APPEND_FOR_LAST_AUDIBLE, // suffix deprecated since API 2
+                    Settings.System.EGG_MODE, // I am the lolrus
+                    Settings.System.END_BUTTON_BEHAVIOR, // bug?
+                    Settings.System
+                            .HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, // candidate for backup?
+                    Settings.System.LOCKSCREEN_DISABLED, // ?
+                    Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
+                    Settings.System.MUTE_STREAMS_AFFECTED, //  candidate for backup?
+                    Settings.System.NOTIFICATION_LIGHT_PULSE, // candidate for backup?
+                    Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
+                    Settings.System.POINTER_LOCATION, // backup candidate?
+                    Settings.System.RINGTONE_CACHE, // internal cache
+                    Settings.System.SCREEN_BRIGHTNESS_FOR_VR, // bug?
+                    Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
+                    Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
+                    Settings.System.SHOW_TOUCHES, // bug?
+                    Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
+                    Settings.System.SIP_ALWAYS, // value, not a setting
+                    Settings.System.SYSTEM_LOCALES, // bug?
+                    Settings.System.USER_ROTATION, // backup candidate?
+                    Settings.System.VIBRATE_IN_SILENT, // deprecated?
+                    Settings.System.VIBRATE_ON, // candidate for backup?
+                    Settings.System.VOLUME_ALARM, // deprecated since API 2?
+                    Settings.System.VOLUME_BLUETOOTH_SCO, // deprecated since API 2?
+                    Settings.System.VOLUME_MASTER, // candidate for backup?
+                    Settings.System.VOLUME_MUSIC, // deprecated since API 2?
+                    Settings.System.VOLUME_NOTIFICATION, // deprecated since API 2?
+                    Settings.System.VOLUME_RING, // deprecated since API 2?
+                    Settings.System.VOLUME_SYSTEM, // deprecated since API 2?
+                    Settings.System.VOLUME_VOICE, // deprecated since API 2?
+                    Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
+                    Settings.System.WINDOW_ORIENTATION_LISTENER_LOG // used for debugging only
+                    );
+
+    private static final Set<String> BACKUP_BLACKLISTED_GLOBAL_SETTINGS =
+            newHashSet(
+                    Settings.Global.ADB_ENABLED,
+                    Settings.Global.ADD_USERS_WHEN_LOCKED,
+                    Settings.Global.AIRPLANE_MODE_ON,
+                    Settings.Global.AIRPLANE_MODE_RADIOS,
+                    Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
+                    Settings.Global.ALARM_MANAGER_CONSTANTS,
+                    Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
+                    Settings.Global.ALWAYS_FINISH_ACTIVITIES,
+                    Settings.Global.ANIMATOR_DURATION_SCALE,
+                    Settings.Global.APN_DB_UPDATE_CONTENT_URL,
+                    Settings.Global.APN_DB_UPDATE_METADATA_URL,
+                    Settings.Global.APP_IDLE_CONSTANTS,
+                    Settings.Global.ASSISTED_GPS_ENABLED,
+                    Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+                    Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
+                    Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
+                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
+                    Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_DISABLED_PROFILES,
+                    Settings.Global.BLUETOOTH_HEADSET_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST,
+                    Settings.Global.BLUETOOTH_MAP_CLIENT_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_MAP_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_ON, // Candidate for backup?
+                    Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_SAP_PRIORITY_PREFIX,
+                    Settings.Global.BOOT_COUNT,
+                    Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL,
+                    Settings.Global.CAPTIVE_PORTAL_HTTPS_URL,
+                    Settings.Global.CAPTIVE_PORTAL_HTTP_URL,
+                    Settings.Global.CAPTIVE_PORTAL_MODE,
+                    Settings.Global.CAPTIVE_PORTAL_SERVER,
+                    Settings.Global.CAPTIVE_PORTAL_USE_HTTPS,
+                    Settings.Global.CAPTIVE_PORTAL_USER_AGENT,
+                    Settings.Global.CAR_DOCK_SOUND,
+                    Settings.Global.CARRIER_APP_WHITELIST,
+                    Settings.Global.CAR_UNDOCK_SOUND,
+                    Settings.Global.CDMA_CELL_BROADCAST_SMS,
+                    Settings.Global.CDMA_ROAMING_MODE,
+                    Settings.Global.CDMA_SUBSCRIPTION_MODE,
+                    Settings.Global.CELL_ON,
+                    Settings.Global.CERT_PIN_UPDATE_CONTENT_URL,
+                    Settings.Global.CERT_PIN_UPDATE_METADATA_URL,
+                    Settings.Global.COMPATIBILITY_MODE,
+                    Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+                    Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE,
+                    Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
+                    Settings.Global.CONTACT_METADATA_SYNC_ENABLED,
+                    Settings.Global.CONTACTS_DATABASE_WAL_ENABLED,
+                    Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
+                    Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+                    Settings.Global.DATABASE_DOWNGRADE_REASON,
+                    Settings.Global.DATA_ROAMING,
+                    Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
+                    Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
+                    Settings.Global.DEBUG_APP,
+                    Settings.Global.DEBUG_VIEW_ATTRIBUTES,
+                    Settings.Global.DEFAULT_DNS_SERVER,
+                    Settings.Global.DEFAULT_INSTALL_LOCATION,
+                    Settings.Global.DESK_DOCK_SOUND,
+                    Settings.Global.DESK_UNDOCK_SOUND,
+                    Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
+                    Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
+                    Settings.Global.DEVELOPMENT_FORCE_RTL,
+                    Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
+                    Settings.Global.DEVICE_DEMO_MODE,
+                    Settings.Global.DEVICE_IDLE_CONSTANTS,
+                    Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH,
+                    Settings.Global.DEVICE_NAME,
+                    Settings.Global.DEVICE_PROVISIONED,
+                    Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
+                    Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
+                    Settings.Global.DISPLAY_SCALING_FORCE,
+                    Settings.Global.DISPLAY_SIZE_FORCED,
+                    Settings.Global.DNS_RESOLVER_MAX_SAMPLES,
+                    Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
+                    Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
+                    Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
+                    Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
+                    Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
+                    Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
+                    Settings.Global.DROPBOX_AGE_SECONDS,
+                    Settings.Global.DROPBOX_MAX_FILES,
+                    Settings.Global.DROPBOX_QUOTA_KB,
+                    Settings.Global.DROPBOX_QUOTA_PERCENT,
+                    Settings.Global.DROPBOX_RESERVE_PERCENT,
+                    Settings.Global.DROPBOX_TAG_PREFIX,
+                    Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
+                    Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
+                    Settings.Global.ENABLE_CELLULAR_ON_BOOT,
+                    Settings.Global.ENABLE_EPHEMERAL_FEATURE,
+                    Settings.Global.ENHANCED_4G_MODE_ENABLED,
+                    Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
+                    Settings.Global.ERROR_LOGCAT_PREFIX,
+                    Settings.Global.FANCY_IME_ANIMATIONS,
+                    Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
+                    Settings.Global.FSTRIM_MANDATORY_INTERVAL,
+                    Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+                    Settings.Global.GLOBAL_HTTP_PROXY_HOST,
+                    Settings.Global.GLOBAL_HTTP_PROXY_PAC,
+                    Settings.Global.GLOBAL_HTTP_PROXY_PORT,
+                    Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
+                    Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
+                    Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
+                    Settings.Global.HDMI_CONTROL_ENABLED,
+                    Settings.Global.HDMI_SYSTEM_AUDIO_ENABLED,
+                    Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+                    Settings.Global.HTTP_PROXY,
+                    Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY,
+                    Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY,
+                    Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL,
+                    Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
+                    Settings.Global.JOB_SCHEDULER_CONSTANTS,
+                    Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+                    Settings.Global.LOCK_SOUND,
+                    Settings.Global.LOW_BATTERY_SOUND,
+                    Settings.Global.LOW_BATTERY_SOUND_TIMEOUT,
+                    Settings.Global.LOW_POWER_MODE,
+                    Settings.Global.LTE_SERVICE_FORCED,
+                    Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
+                    Settings.Global.MDC_INITIAL_MAX_RETRY,
+                    Settings.Global.MHL_INPUT_SWITCHING_ENABLED,
+                    Settings.Global.MHL_POWER_CHARGE_ENABLED,
+                    Settings.Global.MOBILE_DATA, // Candidate for backup?
+                    Settings.Global.MOBILE_DATA_ALWAYS_ON,
+                    Settings.Global.MODE_RINGER,
+                    Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
+                    Settings.Global.MULTI_SIM_SMS_PROMPT,
+                    Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
+                    Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
+                    Settings.Global.MULTI_SIM_VOICE_PROMPT,
+                    Settings.Global.NETSTATS_DEV_BUCKET_DURATION,
+                    Settings.Global.NETSTATS_DEV_DELETE_AGE,
+                    Settings.Global.NETSTATS_DEV_PERSIST_BYTES,
+                    Settings.Global.NETSTATS_DEV_ROTATE_AGE,
+                    Settings.Global.NETSTATS_ENABLED,
+                    Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES,
+                    Settings.Global.NETSTATS_POLL_INTERVAL,
+                    Settings.Global.NETSTATS_SAMPLE_ENABLED,
+                    Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE,
+                    Settings.Global.NETSTATS_UID_BUCKET_DURATION,
+                    Settings.Global.NETSTATS_UID_DELETE_AGE,
+                    Settings.Global.NETSTATS_UID_PERSIST_BYTES,
+                    Settings.Global.NETSTATS_UID_ROTATE_AGE,
+                    Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION,
+                    Settings.Global.NETSTATS_UID_TAG_DELETE_AGE,
+                    Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES,
+                    Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
+                    Settings.Global.NETWORK_AVOID_BAD_WIFI,
+                    Settings.Global.NETWORK_PREFERENCE,
+                    Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
+                    Settings.Global.NETWORK_SCORER_APP,
+                    Settings.Global.NETWORK_SCORING_PROVISIONED,
+                    Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
+                    Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
+                    Settings.Global.NEW_CONTACT_AGGREGATOR,
+                    Settings.Global.NITZ_UPDATE_DIFF,
+                    Settings.Global.NITZ_UPDATE_SPACING,
+                    Settings.Global.NSD_ON,
+                    Settings.Global.NTP_SERVER,
+                    Settings.Global.NTP_TIMEOUT,
+                    Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE,
+                    Settings.Global.OVERLAY_DISPLAY_DEVICES,
+                    Settings.Global.PAC_CHANGE_DELAY,
+                    Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
+                    Settings.Global.PACKAGE_VERIFIER_ENABLE,
+                    Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB,
+                    Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE,
+                    Settings.Global.PACKAGE_VERIFIER_TIMEOUT,
+                    Settings.Global.PDP_WATCHDOG_ERROR_POLL_COUNT,
+                    Settings.Global.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS,
+                    Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
+                    Settings.Global.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT,
+                    Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS,
+                    Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
+                    Settings.Global.POLICY_CONTROL,
+                    Settings.Global.PREFERRED_NETWORK_MODE,
+                    Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
+                    Settings.Global.RADIO_BLUETOOTH,
+                    Settings.Global.RADIO_CELL,
+                    Settings.Global.RADIO_NFC,
+                    Settings.Global.RADIO_WIFI,
+                    Settings.Global.RADIO_WIMAX,
+                    Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
+                    Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
+                    Settings.Global.RETAIL_DEMO_MODE_CONSTANTS,
+                    Settings.Global.SAFE_BOOT_DISALLOWED,
+                    Settings.Global.SAMPLING_PROFILER_MS,
+                    Settings.Global.SELINUX_STATUS,
+                    Settings.Global.SELINUX_UPDATE_CONTENT_URL,
+                    Settings.Global.SELINUX_UPDATE_METADATA_URL,
+                    Settings.Global.SEND_ACTION_APP_ERROR,
+                    Settings.Global.SET_GLOBAL_HTTP_PROXY,
+                    Settings.Global.SET_INSTALL_LOCATION,
+                    Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL,
+                    Settings.Global.SETUP_PREPAID_DETECTION_REDIR_HOST,
+                    Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL,
+                    Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
+                    Settings.Global.SHOW_TEMPERATURE_WARNING,
+                    Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS,
+                    Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT,
+                    Settings.Global.SMS_SHORT_CODE_CONFIRMATION,
+                    Settings.Global.SMS_SHORT_CODE_RULE,
+                    Settings.Global.SMS_SHORT_CODES_UPDATE_CONTENT_URL,
+                    Settings.Global.SMS_SHORT_CODES_UPDATE_METADATA_URL,
+                    Settings.Global.STORAGE_BENCHMARK_INTERVAL,
+                    Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+                    Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
+                    Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
+                    Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
+                    Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
+                    Settings.Global.TCP_DEFAULT_INIT_RWND,
+                    Settings.Global.TETHER_DUN_APN,
+                    Settings.Global.TETHER_DUN_REQUIRED,
+                    Settings.Global.TETHER_SUPPORTED,
+                    Settings.Global.THEATER_MODE_ON,
+                    Settings.Global.TRANSITION_ANIMATION_SCALE,
+                    Settings.Global.TRUSTED_SOUND,
+                    Settings.Global.TZINFO_UPDATE_CONTENT_URL,
+                    Settings.Global.TZINFO_UPDATE_METADATA_URL,
+                    Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS,
+                    Settings.Global.UNLOCK_SOUND,
+                    Settings.Global.USE_GOOGLE_MAIL,
+                    Settings.Global.VT_IMS_ENABLED,
+                    Settings.Global.WAIT_FOR_DEBUGGER,
+                    Settings.Global.WARNING_TEMPERATURE,
+                    Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
+                    Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
+                    Settings.Global.WEBVIEW_MULTIPROCESS,
+                    Settings.Global.WEBVIEW_PROVIDER,
+                    Settings.Global.WFC_IMS_ENABLED,
+                    Settings.Global.WFC_IMS_MODE,
+                    Settings.Global.WFC_IMS_ROAMING_ENABLED,
+                    Settings.Global.WFC_IMS_ROAMING_MODE,
+                    Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
+                    Settings.Global.WIFI_COUNTRY_CODE,
+                    Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
+                    Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON,
+                    Settings.Global.WIFI_DISPLAY_ON,
+                    Settings.Global.WIFI_DISPLAY_WPS_CONFIG,
+                    Settings.Global.WIFI_ENHANCED_AUTO_JOIN,
+                    Settings.Global.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS,
+                    Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
+                    Settings.Global.WIFI_FREQUENCY_BAND,
+                    Settings.Global.WIFI_IDLE_MS,
+                    Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
+                    Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
+                    Settings.Global.WIFI_NETWORK_SHOW_RSSI,
+                    Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+                    Settings.Global.WIFI_NUM_OPEN_NETWORKS_KEPT,
+                    Settings.Global.WIFI_ON,
+                    Settings.Global.WIFI_P2P_DEVICE_NAME,
+                    Settings.Global.WIFI_REENABLE_DELAY_MS,
+                    Settings.Global.WIFI_SAVED_STATE,
+                    Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
+                    Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
+                    Settings.Global.WIFI_SLEEP_POLICY,
+                    Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
+                    Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
+                    Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
+                    Settings.Global.WIFI_WATCHDOG_ON,
+                    Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+                    Settings.Global.WINDOW_ANIMATION_SCALE,
+                    Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
+                    Settings.Global.WTF_IS_FATAL,
+                    Settings.Global.ZEN_MODE,
+                    Settings.Global.ZEN_MODE_CONFIG_ETAG,
+                    Settings.Global.ZEN_MODE_RINGER_LEVEL);
+
+    private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
+             newHashSet(
+                 Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL,
+                 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                 Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
+                 Settings.Secure.ALWAYS_ON_VPN_APP,
+                 Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
+                 Settings.Secure.ANDROID_ID,
+                 Settings.Secure.ANR_SHOW_BACKGROUND,
+                 Settings.Secure.ASSISTANT,
+                 Settings.Secure.ASSIST_DISCLOSURE_ENABLED,
+                 Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
+                 Settings.Secure.ASSIST_STRUCTURE_ENABLED,
+                 Settings.Secure.AUTO_FILL_SERVICE,
+                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED,
+                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
+                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DOWNLOADS_DAYS_TO_RETAIN,
+                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
+                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
+                 Settings.Secure.BACKUP_AUTO_RESTORE,
+                 Settings.Secure.BACKUP_ENABLED,
+                 Settings.Secure.BACKUP_PROVISIONED,
+                 Settings.Secure.BACKUP_TRANSPORT,
+                 Settings.Secure.BLUETOOTH_HCI_LOG,
+                 Settings.Secure.BRIGHTNESS_USE_TWILIGHT,  // Candidate for backup?
+                 Settings.Secure.CARRIER_APPS_HANDLED,
+                 Settings.Secure.COMPLETED_CATEGORY_PREFIX,
+                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
+                 Settings.Secure.DEFAULT_INPUT_METHOD,
+                 Settings.Secure.DEMO_USER_SETUP_COMPLETE,
+                 Settings.Secure.DEVICE_PAIRED,
+                 Settings.Secure.DIALER_DEFAULT_APPLICATION,
+                 Settings.Secure.DISABLED_PRINT_SERVICES,
+                 Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
+                 Settings.Secure.DISPLAY_DENSITY_FORCED,
+                 Settings.Secure.DOWNLOADS_BACKUP_ALLOW_METERED,  // Candidate?
+                 Settings.Secure.DOWNLOADS_BACKUP_CHARGING_ONLY, // Candidate?
+                 Settings.Secure.DOWNLOADS_BACKUP_ENABLED, // Candidate?
+                 Settings.Secure.DOZE_ALWAYS_ON,
+                 Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+                 Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
+                 Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
+                 Settings.Secure.ENABLED_PRINT_SERVICES,
+                 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+                 Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR,
+                 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
+                 Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY,
+                 Settings.Secure.INSTALL_NON_MARKET_APPS,
+                 Settings.Secure.LAST_SETUP_SHOWN,
+                 Settings.Secure.LOCATION_MODE,
+                 Settings.Secure.LOCATION_PREVIOUS_MODE,
+                 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, // Candidate?
+                 Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
+                 Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, // Candidate?
+                 Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
+                 Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
+                 Settings.Secure.MULTI_PRESS_TIMEOUT,
+                 Settings.Secure.NFC_PAYMENT_FOREGROUND,
+                 Settings.Secure.PACKAGE_VERIFIER_STATE,
+                 Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
+                 Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
+                 Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
+                 Settings.Secure.PRINT_SERVICE_SEARCH_URI,
+                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, // Candidate?
+                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, // Candidate?
+                 Settings.Secure.SCREENSAVER_COMPONENTS,
+                 Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, // Candidate?
+                 Settings.Secure.SCREENSAVER_ENABLED, // Candidate?
+                 Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
+                 Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE,
+                 Settings.Secure.SEARCH_MAX_RESULTS_TO_DISPLAY,
+                 Settings.Secure.SEARCH_MAX_SHORTCUTS_RETURNED,
+                 Settings.Secure.SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS,
+                 Settings.Secure.SEARCH_MAX_STAT_AGE_MILLIS,
+                 Settings.Secure.SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING,
+                 Settings.Secure.SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING,
+                 Settings.Secure.SEARCH_NUM_PROMOTED_SOURCES,
+                 Settings.Secure.SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT,
+                 Settings.Secure.SEARCH_PREFILL_MILLIS,
+                 Settings.Secure.SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS,
+                 Settings.Secure.SEARCH_QUERY_THREAD_CORE_POOL_SIZE,
+                 Settings.Secure.SEARCH_QUERY_THREAD_MAX_POOL_SIZE,
+                 Settings.Secure.SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE,
+                 Settings.Secure.SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE,
+                 Settings.Secure.SEARCH_SOURCE_TIMEOUT_MILLIS,
+                 Settings.Secure.SEARCH_THREAD_KEEPALIVE_SECONDS,
+                 Settings.Secure.SEARCH_WEB_RESULTS_OVERRIDE_LIMIT,
+                 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+                 Settings.Secure.SETTINGS_CLASSNAME,
+                 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate?
+                 Settings.Secure.SKIP_FIRST_USE_HINTS, // candidate?
+                 Settings.Secure.SMS_DEFAULT_APPLICATION,
+                 Settings.Secure.TRUST_AGENTS_INITIALIZED,
+                 Settings.Secure.TV_INPUT_CUSTOM_LABELS,
+                 Settings.Secure.TV_INPUT_HIDDEN_INPUTS,
+                 Settings.Secure.UI_NIGHT_MODE, // candidate?
+                 Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS,
+                 Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED,
+                 Settings.Secure.USER_SETUP_COMPLETE,
+                 Settings.Secure.VOICE_INTERACTION_SERVICE,
+                 Settings.Secure.VOICE_RECOGNITION_SERVICE,
+                 Settings.Secure.VR_DISPLAY_MODE, // Candidate?
+                 Settings.Secure.WEB_ACTION_ENABLED);
+
+    @Test
+    public void systemSettingsBackedUpOrBlacklisted() {
+        checkSettingsBackedUpOrBlacklisted(
+                getCandidateSettings(Settings.System.class),
+                newHashSet(Settings.System.SETTINGS_TO_BACKUP),
+                BACKUP_BLACKLISTED_SYSTEM_SETTINGS);
+    }
+
+    @Test
+    public void globalSettingsBackedUpOrBlacklisted() {
+        checkSettingsBackedUpOrBlacklisted(
+            getCandidateSettings(Settings.Global.class),
+            newHashSet(Settings.Global.SETTINGS_TO_BACKUP),
+            BACKUP_BLACKLISTED_GLOBAL_SETTINGS);
+    }
+
+    @Test
+    public void secureSettingsBackedUpOrBlacklisted() {
+        checkSettingsBackedUpOrBlacklisted(
+                getCandidateSettings(Settings.Secure.class),
+                newHashSet(Settings.Secure.SETTINGS_TO_BACKUP),
+            BACKUP_BLACKLISTED_SECURE_SETTINGS);
+    }
+
+    private static void checkSettingsBackedUpOrBlacklisted(
+            Set<String> settings, Set<String> settingsToBackup, Set<String> blacklist) {
+        Set<String> settingsNotBackedUp = difference(settings, settingsToBackup);
+        Set<String> settingsNotBackedUpOrBlacklisted = difference(settingsNotBackedUp, blacklist);
+        assertThat(
+                "Settings not backed up or blacklisted",
+                settingsNotBackedUpOrBlacklisted,
+                is(empty()));
+
+        assertThat(
+            "blacklisted settings backed up",
+            intersect(settingsToBackup, blacklist),
+            is(empty()));
+    }
+
+    private static Set<String> getCandidateSettings(Class<? extends Settings.NameValueTable> clazz) {
+        HashSet<String> result = new HashSet<String>();
+        for (Field field : clazz.getDeclaredFields()) {
+            if (looksLikeValidSetting(field)) {
+                try {
+                    result.add((String) field.get(null));
+                } catch (IllegalAccessException e) {
+                    // Impossible for public fields
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return result;
+    }
+
+    private static boolean looksLikeValidSetting(Field field) {
+        int modifiers = field.getModifiers();
+        return isPublic(modifiers)
+                && isStatic(modifiers)
+                && isFinal(modifiers)
+                && field.getType() == String.class
+                && field.getAnnotation(Deprecated.class) == null;
+    }
+
+    private static <T> Set<T> difference(Set<T> s1, Set<T> s2) {
+        HashSet<T> result = new HashSet<T>(s1);
+        result.removeAll(s2);
+        return result;
+    }
+
+    private static <T> Set<T> intersect(Set<T> s1, Set<T> s2) {
+        HashSet<T> result = new HashSet<T>(s1);
+        result.retainAll(s2);
+        return result;
+    }
+
+}
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
index 97ea885..d89dc63 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
@@ -81,35 +81,23 @@
     public void testVoiceImes() throws Exception {
         // locale: en_US
         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
-                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
+                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
-                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme");
-        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
-                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
-        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
-                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
+                "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
                 "DummyNonDefaultAutoVoiceIme1");
 
         // locale: en_GB
         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
-                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
+                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
-                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme");
-        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
-                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
-        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
-                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
+                "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
                 "DummyNonDefaultAutoVoiceIme1");
 
         // locale: ja_JP
         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
-                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
+                "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
-                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme");
-        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
-                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
-        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
-                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
+                "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
                 "DummyNonDefaultAutoVoiceIme1");
     }
 
@@ -117,54 +105,35 @@
     public void testKeyboardImes() throws Exception {
         // locale: en_US
         assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
-                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
-        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
-                IS_SYSTEM_READY, "com.android.apps.inputmethod.latin",
-                "com.android.apps.inputmethod.voice");
+                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
 
         // locale: en_GB
         assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
-                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
-        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
-                IS_SYSTEM_READY, "com.android.apps.inputmethod.latin",
-                "com.android.apps.inputmethod.voice");
+                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
 
         // locale: en_IN
         assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
-                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
-        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
-                IS_SYSTEM_READY, "com.android.apps.inputmethod.hindi",
+                "com.android.apps.inputmethod.hindi",
                 "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
 
         // locale: hi
         assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
-                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
-        assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
-                IS_SYSTEM_READY, "com.android.apps.inputmethod.hindi",
-                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
+                "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin",
+                "com.android.apps.inputmethod.voice");
 
         // locale: ja_JP
         assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
-                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
-        assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
-                IS_SYSTEM_READY, "com.android.apps.inputmethod.japanese",
-                "com.android.apps.inputmethod.voice");
+                "com.android.apps.inputmethod.japanese", "com.android.apps.inputmethod.voice");
 
         // locale: zh_CN
         assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
-                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
-        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
-                IS_SYSTEM_READY, "com.android.apps.inputmethod.pinyin",
-                "com.android.apps.inputmethod.voice");
+                "com.android.apps.inputmethod.pinyin", "com.android.apps.inputmethod.voice");
 
         // locale: zh_TW
         // Note: In this case, no IME is suitable for the system locale. Hence we will pick up a
         // fallback IME regardless of the "default" attribute.
         assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
-                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
-        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
-                IS_SYSTEM_READY, "com.android.apps.inputmethod.latin",
-                "com.android.apps.inputmethod.voice");
+                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
     }
 
     @SmallTest
@@ -792,10 +761,10 @@
     }
 
     private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
-            final Locale systemLocale, final boolean isSystemReady, String... expectedImeNames) {
+            final Locale systemLocale, String... expectedImeNames) {
         final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
         final String[] actualImeNames = getPackageNames(
-                InputMethodUtils.getDefaultEnabledImes(context, isSystemReady, preinstalledImes));
+                InputMethodUtils.getDefaultEnabledImes(context, preinstalledImes));
         assertEquals(expectedImeNames.length, actualImeNames.length);
         for (int i = 0; i < expectedImeNames.length; ++i) {
             assertEquals(expectedImeNames[i], actualImeNames[i]);
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/CounterParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/CounterParserTest.java
deleted file mode 100644
index 5a7766b..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/CounterParserTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-public class CounterParserTest extends ParserTest {
-
-    public CounterParserTest() {
-        mParser = new CounterParser();
-    }
-
-    public void testGoodData() throws Throwable {
-        String name = "foo";
-        int value = 5;
-        Object[] objects = new Object[2];
-        objects[0] = name;
-        objects[1] = value;
-
-        validateGoodData(name, value, objects);
-    }
-
-    private void validateGoodData(String name, int value, Object[] objects) {
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, times(1)).incrementBy(mNameCaptor.capture(), mCountCaptor.capture());
-
-        assertEquals(TronCounters.TRON_AOSP_PREFIX + name, mNameCaptor.getValue());
-        assertEquals(value, mCountCaptor.getValue().intValue());
-    }
-
-    public void testMissingName() throws Throwable {
-        Object[] objects = new Object[1];
-        objects[0] = 5;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testWrongTypes() throws Throwable {
-        String name = "foo";
-        int value = 5;
-        Object[] objects = new Object[2];
-        objects[0] = value;
-        objects[1] = name;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreMissingInput() throws Throwable {
-        Object[] objects = new Object[0];
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        String name = "foo";
-        int value = 5;
-        Object[] objects = new Object[3];
-        objects[0] = name;
-        objects[1] = value;
-        objects[2] = "foo";
-
-        validateGoodData(name, value, objects);
-    }
-
-
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/HistogramParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/HistogramParserTest.java
deleted file mode 100644
index 1bd9d83..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/HistogramParserTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-public class HistogramParserTest extends ParserTest {
-
-    public HistogramParserTest() {
-        mParser = new HistogramParser();
-    }
-
-    public void testGoodData() throws Throwable {
-        String name = "foo";
-        int bucket = 5;
-        Object[] objects = new Object[2];
-        objects[0] = name;
-        objects[1] = bucket;
-
-        validateGoodData(name, bucket, objects);
-    }
-
-    private void validateGoodData(String name, int bucket, Object[] objects) {
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, times(1))
-                .incrementIntHistogram(mNameCaptor.capture(), mCountCaptor.capture());
-
-        assertEquals(TronCounters.TRON_AOSP_PREFIX + name, mNameCaptor.getValue());
-        assertEquals(bucket, mCountCaptor.getValue().intValue());
-    }
-
-    public void testMissingName() throws Throwable {
-        Object[] objects = new Object[1];
-        objects[0] = 5;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testWrongTypes() throws Throwable {
-        String name = "foo";
-        int value = 5;
-        Object[] objects = new Object[2];
-        objects[0] = value;
-        objects[1] = name;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreMissingInput() throws Throwable {
-        Object[] objects = new Object[0];
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        String name = "foo";
-        int bucket = 5;
-        Object[] objects = new Object[3];
-        objects[0] = name;
-        objects[1] = bucket;
-        objects[2] = "foo";
-
-        validateGoodData(name, bucket, objects);
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
deleted file mode 100644
index f05205d..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-public class NotificationActionClickedParserTest extends ParserTest {
-
-    public NotificationActionClickedParserTest() {
-        mParser = new NotificationActionClickedParser();
-    }
-
-    public void testGoodData() throws Throwable {
-        int t = 1000;
-        int index = 1;
-        Object[] objects = new Object[2];
-        objects[0] = mKey;
-        objects[1] = index;
-
-        validateGoodData(t, "", index, objects);
-    }
-
-    public void testTagged() throws Throwable {
-        int t = 1000;
-        int index = 1;
-        Object[] objects = new Object[2];
-        objects[0] = mTaggedKey;
-        objects[1] = index;
-
-        validateGoodData(t, mTag, index, objects);
-    }
-
-    private LogMaker validateGoodData(int t, String tag, int index, Object[] objects) {
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(MetricsEvent.NOTIFICATION_ITEM_ACTION, proto.getCategory());
-        assertEquals(mKeyPackage, proto.getPackageName());
-        validateNotificationIdAndTag(proto, mId, tag);
-        assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
-        assertEquals(index, proto.getSubtype());
-        return proto;
-    }
-
-    public void testMissingData() throws Throwable {
-        Object[] objects = new Object[0];
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testWrongType() throws Throwable {
-        Object[] objects = new Object[2];
-        objects[0] = 2;
-        objects[1] = 5;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testBadKey() throws Throwable {
-        Object[] objects = new Object[2];
-        objects[0] = "foo";
-        objects[1] = 5;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testMncTimestamps() throws Throwable {
-        int t = 1000;
-        int index = 1;
-        Object[] objects = new Object[5];
-        objects[0] = mKey;
-        objects[1] = index;
-        objects[2] = mSinceCreationMillis;
-        objects[3] = mSinceUpdateMillis;
-        objects[4] = mSinceVisibleMillis;
-
-        LogMaker proto = validateGoodData(t, "", index, objects);
-        validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
-                mSinceVisibleMillis);
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        int t = 1000;
-        int index = 1;
-        Object[] objects = new Object[6];
-        objects[0] = mKey;
-        objects[1] = index;
-        objects[2] = mSinceCreationMillis;
-        objects[3] = mSinceUpdateMillis;
-        objects[4] = mSinceVisibleMillis;
-        objects[5] = "foo";
-
-        validateGoodData(t, "", index, objects);
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
deleted file mode 100644
index 7771e84..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-import org.mockito.ArgumentCaptor;
-
-public class NotificationAlertParserTest extends ParserTest {
-    protected ArgumentCaptor<Boolean> mConfigCaptor;
-
-    private final int mTime = 1000;
-
-    public NotificationAlertParserTest() {
-        mParser = new NotificationAlertParser();
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mConfigCaptor = ArgumentCaptor.forClass(Boolean.class);
-        when(mLogger.getConfig(anyString())).thenReturn(false);
-    }
-
-    public void testBuzzOnly() throws Throwable {
-        Object[] objects = new Object[4];
-        objects[0] = mTaggedKey;
-        objects[1] = 1;
-        objects[2] = 0;
-        objects[3] = 0;
-
-        validateInteraction(true, false, false, objects);
-    }
-
-    public void testBeepOnly() throws Throwable {
-        Object[] objects = new Object[4];
-        objects[0] = mTaggedKey;
-        objects[1] = 0;
-        objects[2] = 1;
-        objects[3] = 0;
-
-        validateInteraction(false, true, false, objects);
-    }
-
-    public void testBlinkOnly() throws Throwable {
-        Object[] objects = new Object[4];
-        objects[0] = mTaggedKey;
-        objects[1] = 0;
-        objects[2] = 0;
-        objects[3] = 1;
-
-        validateInteraction(false, false, true, objects);
-    }
-
-    public void testBuzzBlink() throws Throwable {
-        Object[] objects = new Object[4];
-        objects[0] = mTaggedKey;
-        objects[1] = 1;
-        objects[2] = 0;
-        objects[3] = 1;
-
-        validateInteraction(true, false, true, objects);
-    }
-
-    public void testBeepBlink() throws Throwable {
-        Object[] objects = new Object[4];
-        objects[0] = mTaggedKey;
-        objects[1] = 0;
-        objects[2] = 1;
-        objects[3] = 1;
-
-        validateInteraction(false, true, true, objects);
-    }
-
-    public void testIgnoreExtraArgs() throws Throwable {
-        Object[] objects = new Object[5];
-        objects[0] = mTaggedKey;
-        objects[1] = 0;
-        objects[2] = 1;
-        objects[3] = 1;
-        objects[4] = "foo";
-
-        validateInteraction(false, true, true, objects);
-    }
-
-    private void validateInteraction(boolean buzz, boolean beep, boolean blink, Object[] objects) {
-        int flags = 0;
-        int counts = 0;
-        if (buzz) {
-            counts++;
-            flags |= NotificationAlertParser.BUZZ;
-        }
-        if (beep) {
-            counts++;
-            flags |= NotificationAlertParser.BEEP;
-        }
-        if (blink) {
-            counts++;
-            flags |= NotificationAlertParser.BLINK;
-        }
-
-        mParser.parseEvent(mLogger, mTime, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(mTime, proto.getTimestamp());
-        assertEquals(MetricsEvent.NOTIFICATION_ALERT, proto.getCategory());
-        assertEquals(mKeyPackage, proto.getPackageName());
-        validateNotificationIdAndTag(proto, mId, mTag);
-        assertEquals(flags, proto.getSubtype());
-        assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
-
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
deleted file mode 100644
index 77b2ed6..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Mockito.anyObject;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-public class NotificationCanceledParserTest extends ParserTest {
-
-    public NotificationCanceledParserTest() {
-        mParser = new NotificationCanceledParser();
-    }
-
-    public void testGoodProto() throws Throwable {
-        int t = 1000;
-        int reason = NotificationCanceledParser.REASON_DELEGATE_CANCEL;
-        Object[] objects = new Object[2];
-        objects[0] = mKey;
-        objects[1] = reason;
-
-        validateGoodData(t, "", reason, objects);
-    }
-
-    public void testTagged() throws Throwable {
-        int t = 1000;
-        int reason = NotificationCanceledParser.REASON_DELEGATE_CANCEL;
-        Object[] objects = new Object[2];
-        objects[0] = mTaggedKey;
-        objects[1] = reason;
-
-        validateGoodData(t, mTag, reason, objects);
-    }
-
-    private void validateGoodData(int t, String tag, int reason, Object[] objects) {
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
-        assertEquals(mKeyPackage, proto.getPackageName());
-        validateNotificationIdAndTag(proto, mId, tag);
-        assertEquals(MetricsEvent.TYPE_DISMISS, proto.getType());
-        assertEquals(reason, proto.getSubtype());
-    }
-
-    public void testLifetime() throws Throwable {
-        int t = 1000;
-        int reason = NotificationCanceledParser.REASON_DELEGATE_CANCEL;
-        Object[] objects = new Object[3];
-        objects[0] = mKey;
-        objects[1] = reason;
-        objects[2] = mSinceCreationMillis;
-
-        validateTimers(t, objects, mSinceCreationMillis, 0, 0);
-    }
-
-    public void testExposure() throws Throwable {
-        int t = 1000;
-        int reason = NotificationCanceledParser.REASON_DELEGATE_CANCEL;
-        Object[] objects = new Object[4];
-        objects[0] = mKey;
-        objects[1] = reason;
-        objects[2] = mSinceCreationMillis;
-        objects[3] = mSinceVisibleMillis;
-
-
-        validateTimers(t, objects, mSinceCreationMillis, 0, mSinceVisibleMillis);
-    }
-
-    public void testFreshness() throws Throwable {
-        int t = 1000;
-        int reason = NotificationCanceledParser.REASON_DELEGATE_CANCEL;
-        Object[] objects = new Object[5];
-        objects[0] = mKey;
-        objects[1] = reason;
-        objects[2] = mSinceCreationMillis;
-        objects[3] = mSinceUpdateMillis;
-        objects[4] = mSinceVisibleMillis;
-
-        validateTimers(t, objects, mSinceCreationMillis, mSinceUpdateMillis, mSinceVisibleMillis);
-    }
-
-    private void validateTimers(int t, Object[] objects, int life, int freshness, int exposure) {
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        validateNotificationTimes(proto, life, freshness, exposure);
-    }
-
-    public void verifyReason(int reason, boolean intentional, boolean important, String counter)
-            throws Throwable {
-        Object[] objects = new Object[2];
-        objects[0] = mKey;
-        objects[1] = reason;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        if (intentional) {
-            verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
-        }
-    }
-
-    public void testDelegateCancel() throws Throwable {
-        verifyReason(NotificationCanceledParser.REASON_DELEGATE_CANCEL,
-                true, true, TronCounters.TRON_NOTE_DISMISS_BY_USER);
-    }
-
-    public void testDelegateCancelAll() throws Throwable {
-        verifyReason(NotificationCanceledParser.REASON_DELEGATE_CANCEL_ALL,
-                true, true, TronCounters.TRON_NOTE_DISMISS_BY_USER);
-    }
-
-    public void testListenerCancel() throws Throwable {
-        verifyReason(NotificationCanceledParser.REASON_LISTENER_CANCEL,
-                false, true, TronCounters.TRON_NOTE_DISMISS_BY_LISTENER);
-    }
-
-    public void testListenerCancelAll() throws Throwable {
-        verifyReason(NotificationCanceledParser.REASON_LISTENER_CANCEL_ALL,
-                false, true, TronCounters.TRON_NOTE_DISMISS_BY_LISTENER);
-    }
-
-    public void testDelegateClick() throws Throwable {
-        verifyReason(NotificationCanceledParser.REASON_DELEGATE_CLICK,
-                true, true, TronCounters.TRON_NOTE_DISMISS_BY_CLICK);
-    }
-
-    public void testBanned() throws Throwable {
-        verifyReason(NotificationCanceledParser.REASON_PACKAGE_BANNED,
-                false, true, TronCounters.TRON_NOTE_DISMISS_BY_BAN);
-    }
-
-    public void testUnknownReason() throws Throwable {
-        verifyReason(1001010, false, false, TronCounters.TRON_NOTE_DISMISS_BY_BAN);
-    }
-
-    public void testMissingData() throws Throwable {
-        Object[] objects = new Object[0];
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testWrongType() throws Throwable {
-        Object[] objects = new Object[2];
-        objects[0] = 2;
-        objects[1] = 5;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testBadKey() throws Throwable {
-        Object[] objects = new Object[2];
-        objects[0] = "foo";
-        objects[1] = 5;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        int t = 1000;
-        int reason = NotificationCanceledParser.REASON_DELEGATE_CANCEL;
-        Object[] objects = new Object[3];
-        objects[0] = mKey;
-        objects[1] = reason;
-        objects[2] = "foo";
-
-        validateGoodData(t, "", reason, objects);
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
deleted file mode 100644
index cc65132..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-public class NotificationClickedParserTest extends ParserTest {
-
-    public NotificationClickedParserTest() {
-        mParser = new NotificationClickedParser();
-    }
-
-    public void testGoodData() throws Throwable {
-        int t = 1000;
-        Object[] objects = new Object[1];
-        objects[0] = mKey;
-
-        validateGoodData(t, "", objects);
-    }
-
-    public void testTagged() throws Throwable {
-        int t = 1000;
-        Object[] objects = new Object[1];
-        objects[0] = mTaggedKey;
-
-        validateGoodData(t, mTag, objects);
-    }
-
-    private LogMaker validateGoodData(int t, String tag, Object[] objects) {
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
-        assertEquals(mKeyPackage, proto.getPackageName());
-        validateNotificationIdAndTag(proto, mId, tag);
-        assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
-        assertEquals(0, proto.getSubtype());
-        return proto;
-    }
-
-    public void testMissingKey() throws Throwable {
-        Object[] objects = new Object[0];
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testWrongType() throws Throwable {
-        Object[] objects = new Object[1];
-        objects[0] = 5;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testBadKey() throws Throwable {
-        Object[] objects = new Object[1];
-        objects[0] = "foo";
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testMncTimestamps() throws Throwable {
-        int t = 1000;
-        Object[] objects = new Object[4];
-        objects[0] = mKey;
-        objects[1] = mSinceCreationMillis;
-        objects[2] = mSinceUpdateMillis;
-        objects[3] = mSinceVisibleMillis;
-
-        LogMaker proto = validateGoodData(t, "", objects);
-        validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
-                mSinceVisibleMillis);
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        int t = 1000;
-        Object[] objects = new Object[5];
-        objects[0] = mKey;
-        objects[1] = mSinceCreationMillis;
-        objects[2] = mSinceUpdateMillis;
-        objects[3] = mSinceVisibleMillis;
-        objects[4] = "foo";
-
-        validateGoodData(t, "", objects);
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
deleted file mode 100644
index f337f91..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-public class NotificationExpansionParserTest extends ParserTest {
-
-    public NotificationExpansionParserTest() {
-        mParser = new NotificationExpansionParser();
-    }
-
-    public void testExpandedByUser() throws Throwable {
-        int t = 1000;
-        int byUser = 1;
-        int expanded = 1;
-        Object[] objects = new Object[3];
-        objects[0] = mKey;
-        objects[1] = byUser;
-        objects[2] = expanded;
-
-        validateGoodData(t, "", objects);
-    }
-
-    public void testTagged() throws Throwable {
-        int t = 1000;
-        int byUser = 1;
-        int expanded = 1;
-        Object[] objects = new Object[3];
-        objects[0] = mTaggedKey;
-        objects[1] = byUser;
-        objects[2] = expanded;
-
-        validateGoodData(t, mTag, objects);
-    }
-
-    private LogMaker validateGoodData(int t, String tag, Object[] objects) {
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
-        assertEquals(mKeyPackage, proto.getPackageName());
-        validateNotificationIdAndTag(proto, mId, tag);
-        assertEquals(MetricsEvent.TYPE_DETAIL, proto.getType());
-        return proto;
-    }
-
-    public void testAutoExpand() throws Throwable {
-        int t = 1000;
-        int byUser = 0;
-        int expanded = 1;
-        Object[] objects = new Object[3];
-        objects[0] = mKey;
-        objects[1] = byUser;
-        objects[2] = expanded;
-
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testCollapsedByUser() throws Throwable {
-        int t = 1000;
-        int byUser = 1;
-        int expanded = 0;
-        Object[] objects = new Object[3];
-        objects[0] = mKey;
-        objects[1] = byUser;
-        objects[2] = expanded;
-
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testAutoCollapsed() throws Throwable {
-        int t = 1000;
-        int byUser = 0;
-        int expanded = 0;
-        Object[] objects = new Object[3];
-        objects[0] = mKey;
-        objects[1] = byUser;
-        objects[2] = expanded;
-
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testMissingData() throws Throwable {
-        Object[] objects = new Object[0];
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testWrongType() throws Throwable {
-        Object[] objects = new Object[3];
-        objects[0] = 2;
-        objects[1] = 5;
-        objects[2] = 7;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testBadKey() throws Throwable {
-        Object[] objects = new Object[3];
-        objects[0] = "foo";
-        objects[1] = 5;
-        objects[2] = 2;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-    }
-
-    public void testMncTimestamps() throws Throwable {
-        int t = 1000;
-        int byUser = 1;
-        int expanded = 1;
-        Object[] objects = new Object[6];
-        objects[0] = mKey;
-        objects[1] = byUser;
-        objects[2] = expanded;
-        objects[3] = mSinceCreationMillis;
-        objects[4] = mSinceUpdateMillis;
-        objects[5] = mSinceVisibleMillis;
-
-        LogMaker proto = validateGoodData(t, "", objects);
-        validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
-                mSinceVisibleMillis);
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        int t = 1000;
-        int byUser = 1;
-        int expanded = 1;
-        Object[] objects = new Object[7];
-        objects[0] = mKey;
-        objects[1] = byUser;
-        objects[2] = expanded;
-        objects[3] = mSinceCreationMillis;
-        objects[4] = mSinceUpdateMillis;
-        objects[5] = mSinceVisibleMillis;
-        objects[6] = "foo";
-
-        validateGoodData(t, "", objects);
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationKeyTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationKeyTest.java
deleted file mode 100644
index b509700..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationKeyTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import android.test.InstrumentationTestCase;
-
-public class NotificationKeyTest extends InstrumentationTestCase {
-
-    private final NotificationKey mKey;
-
-    public NotificationKeyTest() {
-        mKey = new NotificationKey();
-    }
-
-    public void testGoodKey() throws Throwable {
-        assertTrue(mKey.parse("1|com.android.example.notificationshowcase|31338|null|10090"));
-
-        assertEquals("com.android.example.notificationshowcase", mKey.mPackageName);
-        assertEquals("", mKey.mTag);
-        assertEquals(31338, mKey.mId);
-        assertEquals(1, mKey.mUser);
-        assertEquals(10090, mKey.mUid);
-    }
-
-    public void testTaggedKey() throws Throwable {
-        assertTrue(mKey.parse("1|com.android.example.notificationshowcase|31338|foo|10090"));
-
-        assertEquals("com.android.example.notificationshowcase", mKey.mPackageName);
-        assertEquals("foo", mKey.mTag);
-        assertEquals(31338, mKey.mId);
-        assertEquals(1, mKey.mUser);
-        assertEquals(10090, mKey.mUid);
-    }
-
-    public void testEmptyTag() throws Throwable {
-        assertTrue(mKey.parse("1|com.android.example.notificationshowcase|31338||10090"));
-
-        assertEquals("com.android.example.notificationshowcase", mKey.mPackageName);
-        assertEquals("", mKey.mTag);
-        assertEquals(31338, mKey.mId);
-        assertEquals(1, mKey.mUser);
-        assertEquals(10090, mKey.mUid);
-    }
-
-    public void testBadKeys() throws Throwable {
-        assertFalse(mKey.parse(null));
-        assertFalse(mKey.parse(""));
-        assertFalse(mKey.parse("foo"));  // not a key
-        assertFalse(mKey.parse("1|com.android.example.notificationshowcase|31338|null"));
-        assertFalse(mKey.parse("bar|com.android.example.notificationshowcase|31338|null|10090"));
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
deleted file mode 100644
index ce6f1f4..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-public class NotificationPanelHiddenParserTest extends ParserTest {
-
-    public NotificationPanelHiddenParserTest() {
-        mParser = new NotificationPanelHiddenParser();
-    }
-
-    public void testNoInput() throws Throwable {
-        int t = 1000;
-        Object[] objects = new Object[0];
-
-        validateGoodData(t, objects);
-
-    }
-
-    public void testIgnoreExtraneousInput() throws Throwable {
-        Object[] objects = new Object[1];
-        objects[0] = "nothing to see here";
-
-        validateGoodData(0, objects);
-    }
-
-    private void validateGoodData(int t, Object[] objects) {
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
-        assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType());
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
deleted file mode 100644
index 9e15812..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-public class NotificationPanelRevealedParserTest extends ParserTest {
-
-    public NotificationPanelRevealedParserTest() {
-        mParser = new NotificationPanelRevealedParser();
-    }
-
-    public void testLollipopInput() throws Throwable {
-        int t = 1000;
-        Object[] objects = new Object[0];
-
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
-        assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
-    }
-
-    public void testMncData() throws Throwable {
-        int t = 1000;
-        int n = 5;
-        Object[] objects = new Object[1];
-        objects[0] = Integer.valueOf(n);
-
-        validateMncData(t, n, objects);
-    }
-
-    private void validateMncData(int t, int n, Object[] objects) {
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
-        assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
-    }
-
-    public void testBadInput() throws Throwable {
-        Object[] objects = new Object[1];
-        objects[0] = "This is not the integer you're looking for.";
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        int t = 1000;
-        int n = 5;
-        Object[] objects = new Object[2];
-        objects[0] = Integer.valueOf(n);
-        objects[1] = "foo";
-
-        validateMncData(t, n, objects);
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
deleted file mode 100644
index 7fef929..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-public class NotificationVisibilityParserTest extends ParserTest {
-    private final int mCreationTime = 23124;
-    private final int mUpdateTime = 3412;
-    private final int mTime = 1000;
-
-    public NotificationVisibilityParserTest() {
-        mParser = new NotificationVisibilityParser();
-    }
-
-    public void testReveal() throws Throwable {
-        Object[] objects = new Object[4];
-        objects[0] = mTaggedKey;
-        objects[1] = 1;
-        objects[2] = mCreationTime;
-        objects[3] = mUpdateTime;
-
-        validateInteraction(true, mUpdateTime, 0, objects);
-    }
-
-    public void testHide() throws Throwable {
-        Object[] objects = new Object[4];
-        objects[0] = mTaggedKey;
-        objects[1] = 0;
-        objects[2] = mCreationTime;
-        objects[3] = mUpdateTime;
-
-        validateInteraction(false, mUpdateTime, 0, objects);
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        Object[] objects = new Object[5];
-        objects[0] = mTaggedKey;
-        objects[1] = 1;
-        objects[2] = mCreationTime;
-        objects[3] = mUpdateTime;
-        objects[4] = "foo";
-
-        validateInteraction(true, mUpdateTime, 0, objects);
-    }
-
-    public void testMarshmallowIndexData() throws Throwable {
-        Object[] objects = new Object[6];
-        objects[0] = mTaggedKey;
-        objects[1] = 1;
-        objects[2] = mCreationTime;
-        objects[3] = mUpdateTime;
-        objects[4] = 0;
-        objects[5] = 3;
-
-        validateInteraction(true, mUpdateTime, 3, objects);
-    }
-
-    private void validateInteraction(boolean visible, int freshness, int index, Object[] objects)    {
-        mParser.parseEvent(mLogger, mTime, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(mTime, proto.getTimestamp());
-        assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
-        assertEquals(mKeyPackage, proto.getPackageName());
-        validateNotificationIdAndTag(proto, mId, mTag);
-        validateNotificationTimes(proto, mCreationTime, mUpdateTime);
-        assertEquals(index, proto.getTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX));
-        assertEquals(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE, proto.getType());
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
deleted file mode 100644
index 2ad76c1..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-public class SysuiActionParserTest extends ParserTest {
-
-    public SysuiActionParserTest() {
-        mParser = new SysuiActionParser();
-    }
-
-    public void testGoodDatal() throws Throwable {
-        int t = 1000;
-        int view = 10;
-        Object[] objects = new Object[1];
-        objects[0] = view;
-
-        validateGoodData(t, view, objects);
-    }
-
-    private void validateGoodData(int t, int view, Object[] objects) {
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(view, proto.getCategory());
-        assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
-    }
-
-    public void testGoodDataWithPackage() throws Throwable {
-        int t = 1000;
-        int view = 10;
-        String packageName = "com.foo";
-        Object[] objects = new Object[2];
-        objects[0] = view;
-        objects[1] = packageName;
-
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(view, proto.getCategory());
-        assertEquals(packageName, proto.getPackageName());
-        assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
-    }
-
-    public void testGoodDataWithTrue() throws Throwable {
-        validateSubType(Boolean.toString(true), 1);
-    }
-
-    public void testGoodDataWithFalse() throws Throwable {
-        validateSubType(Boolean.toString(false), 0);
-    }
-
-    public void testGoodDataWithIntZero() throws Throwable {
-        validateSubType(Integer.toString(0), 0);
-    }
-
-    public void testGoodDataWithIntONe() throws Throwable {
-        validateSubType(Integer.toString(1), 1);
-    }
-
-    public void testGoodDataWithIntTwo() throws Throwable {
-        validateSubType(Integer.toString(2), 2);
-    }
-
-    public void testGoodDataWithNegativeInt() throws Throwable {
-        validateSubType(Integer.toString(-1), -1);
-    }
-
-    public void testGoodDataWithIntLarge() throws Throwable {
-        validateSubType(Integer.toString(120312), 120312);
-    }
-
-    public void testGoodDataWithNegativeIntLarge() throws Throwable {
-        validateSubType(Integer.toString(-120312), -120312);
-    }
-
-    private void validateSubType(String arg, int expectedValue) {
-        int t = 1000;
-        int view = 10;
-        Object[] objects = new Object[2];
-        objects[0] = view;
-        objects[1] = arg;
-
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(view, proto.getCategory());
-        assertEquals(expectedValue, proto.getSubtype());
-        assertNull(proto.getPackageName());
-        assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
-    }
-
-    public void testIgnoreMissingInput() throws Throwable {
-        Object[] objects = new Object[0];
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreWrongInputs() throws Throwable {
-        Object[] objects = new Object[2];
-        objects[0] = "nothing to see here";
-        objects[1] = 10;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreStringViewInput() throws Throwable {
-        Object[] objects = new Object[1];
-        objects[0] = "this is not the input you are looking for";
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        int t = 1000;
-        int view = 10;
-        Object[] objects = new Object[2];
-        objects[0] = view;
-        objects[1] = "foo";
-
-        validateGoodData(t, view, objects);
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
deleted file mode 100644
index 64d69a4..0000000
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.internal.logging.legacy;
-
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-
-import android.metrics.LogMaker;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-public class SysuiViewVisibilityParserTest extends ParserTest {
-
-    public SysuiViewVisibilityParserTest() {
-        mParser = new SysuiViewVisibilityParser();
-    }
-
-    public void testViewReveal() throws Throwable {
-        int t = 1000;
-        int view = 10;
-        Object[] objects = new Object[2];
-        objects[0] = view;
-        objects[1] = 100;
-
-        validateViewReveal(t, view, objects);
-    }
-
-    private void validateViewReveal(int t, int view, Object[] objects) {
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(t, proto.getTimestamp());
-        assertEquals(view, proto.getCategory());
-        assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
-    }
-
-    public void testViewHidden() throws Throwable {
-        int t = 1000;
-        int view = 10;
-        Object[] objects = new Object[2];
-        objects[0] = view;
-        objects[1] = 0;
-
-        mParser.parseEvent(mLogger, t, objects);
-
-        verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
-
-        LogMaker proto = mProtoCaptor.getValue();
-        assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType());
-    }
-
-    public void testIgnoreMissingInput() throws Throwable {
-        Object[] objects = new Object[0];
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreStringInARgOne() throws Throwable {
-        Object[] objects = new Object[2];
-        objects[0] = "nothing to see here";
-        objects[1] = 100;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreStringInArgTwo() throws Throwable {
-        Object[] objects = new Object[2];
-        objects[0] = 100;
-        objects[1] = "nothing to see here";
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testOneInput() throws Throwable {
-        Object[] objects = new Object[1];
-        objects[0] = 100;
-
-        mParser.parseEvent(mLogger, 0, objects);
-
-        verify(mLogger, never()).addEvent((LogMaker) anyObject());
-        verify(mLogger, never()).incrementBy(anyString(), anyInt());
-    }
-
-    public void testIgnoreUnexpectedData() throws Throwable {
-        int t = 1000;
-        int view = 10;
-        Object[] objects = new Object[3];
-        objects[0] = view;
-        objects[1] = 100;
-        objects[2] = "foo";
-
-        validateViewReveal(t, view, objects);
-    }
-}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 38e8061..2e6a901 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.res.AssetManager;
 import android.graphics.fonts.FontRequest;
 import android.graphics.fonts.FontResult;
@@ -131,9 +132,9 @@
 
     /**
      * @hide
-     * Used by Resources.
+     * Used by Resources to load a font resource of type font file.
      */
-    @NonNull
+    @Nullable
     public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
         if (sFallbackFonts != null) {
             synchronized (sDynamicTypefaceCache) {
@@ -143,6 +144,7 @@
 
                 FontFamily fontFamily = new FontFamily();
                 if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */)) {
+                    fontFamily.freeze();
                     FontFamily[] families = {fontFamily};
                     typeface = createFromFamiliesWithDefault(families);
                     sDynamicTypefaceCache.put(key, typeface);
@@ -150,7 +152,62 @@
                 }
             }
         }
-        throw new RuntimeException("Font resource not found " + path);
+        return null;
+    }
+
+    /**
+     * @hide
+     * Used by Resources to load a font resource of type xml.
+     */
+    @Nullable
+    public static Typeface createFromResources(FontConfig config, AssetManager mgr, String path) {
+        if (sFallbackFonts != null) {
+            synchronized (sDynamicTypefaceCache) {
+                final String key = createAssetUid(mgr, path);
+                Typeface typeface = sDynamicTypefaceCache.get(key);
+                if (typeface != null) return typeface;
+
+                List<FontConfig.Family> families = config.getFamilies();
+                if (families == null || families.isEmpty()) {
+                    throw new RuntimeException("Font resource contained no fonts.");
+                }
+                if (families.size() > 1) {
+                    throw new RuntimeException("Font resource contained more than one family.");
+                }
+                FontConfig.Family family = families.get(0);
+
+                FontFamily fontFamily = new FontFamily();
+                List<FontConfig.Font> fonts = family.getFonts();
+                for (int i = 0; i < fonts.size(); i++) {
+                    FontConfig.Font font = fonts.get(i);
+                    // TODO: Use style and weight info
+                    if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(),
+                            0 /* resourceCookie */, false /* isAsset */)) {
+                        return null;
+                    }
+                }
+                fontFamily.freeze();
+                FontFamily[] familyChain = { fontFamily };
+                typeface = createFromFamiliesWithDefault(familyChain);
+                sDynamicTypefaceCache.put(key, typeface);
+                return typeface;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public static Typeface createFromCache(AssetManager mgr, String path) {
+        synchronized (sDynamicTypefaceCache) {
+            final String key = createAssetUid(mgr, path);
+            Typeface typeface = sDynamicTypefaceCache.get(key);
+            if (typeface != null) {
+                return typeface;
+            }
+        }
+        return null;
     }
 
     /**
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index fb89835..ecf6bd4 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -38,6 +38,7 @@
         "ResourceTypes.cpp",
         "StreamingZipInflater.cpp",
         "TypeWrappers.cpp",
+        "Util.cpp",
         "ZipFileRO.cpp",
         "ZipUtils.cpp",
     ],
@@ -82,6 +83,9 @@
         },
         windows: {
             enabled: true,
+            cppflags: ["-Wno-missing-field-initializers"],  // The Windows compiler warns
+                                                            // incorrectly for value
+                                                            // initialization with {}.
         },
     },
 }
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 55f4c3c..9a08f63 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -28,7 +28,16 @@
 namespace android {
 
 std::unique_ptr<ApkAssets> ApkAssets::Load(const std::string& path) {
-  ATRACE_NAME("ApkAssets::Load");
+  return ApkAssets::LoadImpl(path, false /*load_as_shared_library*/);
+}
+
+std::unique_ptr<ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path) {
+  return ApkAssets::LoadImpl(path, true /*load_as_shared_library*/);
+}
+
+std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(const std::string& path,
+                                               bool load_as_shared_library) {
+  ATRACE_CALL();
   ::ZipArchiveHandle unmanaged_handle;
   int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
   if (result != 0) {
@@ -61,7 +70,7 @@
   loaded_apk->path_ = path;
   loaded_apk->loaded_arsc_ =
       LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/),
-                       loaded_apk->resources_asset_->getLength());
+                       loaded_apk->resources_asset_->getLength(), load_as_shared_library);
   if (loaded_apk->loaded_arsc_ == nullptr) {
     return {};
   }
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 8d65925..d2eff65 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -36,13 +36,84 @@
 bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
                                  bool invalidate_caches) {
   apk_assets_ = apk_assets;
+  BuildDynamicRefTable();
   if (invalidate_caches) {
     InvalidateCaches(static_cast<uint32_t>(-1));
   }
   return true;
 }
 
-const std::vector<const ApkAssets*> AssetManager2::GetApkAssets() const { return apk_assets_; }
+void AssetManager2::BuildDynamicRefTable() {
+  package_groups_.clear();
+  package_ids_.fill(0xff);
+
+  // 0x01 is reserved for the android package.
+  int next_package_id = 0x02;
+  const size_t apk_assets_count = apk_assets_.size();
+  for (size_t i = 0; i < apk_assets_count; i++) {
+    const ApkAssets* apk_asset = apk_assets_[i];
+    for (const std::unique_ptr<const LoadedPackage>& package :
+         apk_asset->GetLoadedArsc()->GetPackages()) {
+      // Get the package ID or assign one if a shared library.
+      int package_id;
+      if (package->IsDynamic()) {
+        package_id = next_package_id++;
+      } else {
+        package_id = package->GetPackageId();
+      }
+
+      // Add the mapping for package ID to index if not present.
+      uint8_t idx = package_ids_[package_id];
+      if (idx == 0xff) {
+        package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
+        package_groups_.push_back({});
+        package_groups_.back().dynamic_ref_table.mAssignedPackageId = package_id;
+      }
+      PackageGroup* package_group = &package_groups_[idx];
+
+      // Add the package and to the set of packages with the same ID.
+      package_group->packages_.push_back(package.get());
+      package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
+
+      // Add the package name -> build time ID mappings.
+      for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) {
+        String16 package_name(entry.package_name.c_str(), entry.package_name.size());
+        package_group->dynamic_ref_table.mEntries.replaceValueFor(
+            package_name, static_cast<uint8_t>(entry.package_id));
+      }
+    }
+  }
+
+  // Now assign the runtime IDs so that we have a build-time to runtime ID map.
+  const auto package_groups_end = package_groups_.end();
+  for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
+    const std::string& package_name = iter->packages_[0]->GetPackageName();
+    for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
+      iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()),
+                                          iter->dynamic_ref_table.mAssignedPackageId);
+    }
+  }
+}
+
+void AssetManager2::DumpToLog() const {
+  base::ScopedLogSeverity _log(base::INFO);
+
+  std::string list;
+  for (size_t i = 0; i < package_ids_.size(); i++) {
+    if (package_ids_[i] != 0xff) {
+      base::StringAppendF(&list, "%02x -> %d, ", (int) i, package_ids_[i]);
+    }
+  }
+  LOG(INFO) << "Package ID map: " << list;
+
+  for (const auto& package_group: package_groups_) {
+      list = "";
+      for (const auto& package : package_group.packages_) {
+        base::StringAppendF(&list, "%s(%02x), ", package->GetPackageName().c_str(), package->GetPackageId());
+      }
+      LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list;
+  }
+}
 
 const ResStringPool* AssetManager2::GetStringPoolForCookie(ApkAssetsCookie cookie) const {
   if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
@@ -51,6 +122,18 @@
   return apk_assets_[cookie]->GetLoadedArsc()->GetStringPool();
 }
 
+const DynamicRefTable* AssetManager2::GetDynamicRefTableForPackage(uint32_t package_id) const {
+  if (package_id >= package_ids_.size()) {
+    return nullptr;
+  }
+
+  const size_t idx = package_ids_[package_id];
+  if (idx == 0xff) {
+    return nullptr;
+  }
+  return &package_groups_[idx].dynamic_ref_table;
+}
+
 void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
   const int diff = configuration_.diff(configuration);
   configuration_ = configuration;
@@ -60,8 +143,6 @@
   }
 }
 
-const ResTable_config& AssetManager2::GetConfiguration() const { return configuration_; }
-
 std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) {
   const std::string new_path = "assets/" + filename;
   return OpenNonAsset(new_path, mode);
@@ -106,7 +187,7 @@
 }
 
 ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
-                                         bool stop_at_first_match, LoadedArsc::Entry* out_entry,
+                                         bool stop_at_first_match, LoadedArscEntry* out_entry,
                                          ResTable_config* out_selected_config,
                                          uint32_t* out_flags) {
   ATRACE_CALL();
@@ -122,48 +203,66 @@
     desired_config = &density_override_config;
   }
 
-  LoadedArsc::Entry best_entry;
+  const uint32_t package_id = util::get_package_id(resid);
+  const uint8_t type_id = util::get_type_id(resid);
+  const uint16_t entry_id = util::get_entry_id(resid);
+
+  if (type_id == 0) {
+    LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
+    return kInvalidCookie;
+  }
+
+  const uint8_t idx = package_ids_[package_id];
+  if (idx == 0xff) {
+    LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);
+    return kInvalidCookie;
+  }
+
+  LoadedArscEntry best_entry;
   ResTable_config best_config;
-  int32_t best_index = -1;
-  uint32_t cumulated_flags = 0;
+  ApkAssetsCookie best_cookie = kInvalidCookie;
+  uint32_t cumulated_flags = 0u;
 
-  const size_t apk_asset_count = apk_assets_.size();
-  for (size_t i = 0; i < apk_asset_count; i++) {
-    const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();
-
-    LoadedArsc::Entry current_entry;
+  const PackageGroup& package_group = package_groups_[idx];
+  const size_t package_count = package_group.packages_.size();
+  for (size_t i = 0; i < package_count; i++) {
+    LoadedArscEntry current_entry;
     ResTable_config current_config;
-    uint32_t flags = 0;
-    if (!loaded_arsc->FindEntry(resid, *desired_config, &current_entry, &current_config, &flags)) {
+    uint32_t current_flags = 0;
+
+    const LoadedPackage* loaded_package = package_group.packages_[i];
+    if (!loaded_package->FindEntry(type_id - 1, entry_id, *desired_config, &current_entry,
+                                   &current_config, &current_flags)) {
       continue;
     }
 
-    cumulated_flags |= flags;
+    cumulated_flags |= current_flags;
 
-    if (best_index == -1 || current_config.isBetterThan(best_config, desired_config)) {
+    if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config)) {
       best_entry = current_entry;
       best_config = current_config;
-      best_index = static_cast<int32_t>(i);
+      best_cookie = package_group.cookies_[i];
       if (stop_at_first_match) {
         break;
       }
     }
   }
 
-  if (best_index == -1) {
+  if (best_cookie == kInvalidCookie) {
     return kInvalidCookie;
   }
 
   *out_entry = best_entry;
+  out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
   *out_selected_config = best_config;
   *out_flags = cumulated_flags;
-  return best_index;
+  return best_cookie;
 }
 
 bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
   ATRACE_CALL();
 
-  LoadedArsc::Entry entry;
+  LoadedArscEntry entry;
   ResTable_config config;
   uint32_t flags = 0u;
   ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
@@ -172,14 +271,13 @@
     return false;
   }
 
-  const std::string* package_name =
-      apk_assets_[cookie]->GetLoadedArsc()->GetPackageNameForId(resid);
-  if (package_name == nullptr) {
+  const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid);
+  if (package == nullptr) {
     return false;
   }
 
-  out_name->package = package_name->data();
-  out_name->package_len = package_name->size();
+  out_name->package = package->GetPackageName().data();
+  out_name->package_len = package->GetPackageName().size();
 
   out_name->type = entry.type_string_ref.string8(&out_name->type_len);
   out_name->type16 = nullptr;
@@ -202,7 +300,7 @@
 }
 
 bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
-  LoadedArsc::Entry entry;
+  LoadedArscEntry entry;
   ResTable_config config;
   ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
                                      false /* stop_at_first_match */, &entry, &config, out_flags);
@@ -215,7 +313,7 @@
                                            uint32_t* out_flags) {
   ATRACE_CALL();
 
-  LoadedArsc::Entry entry;
+  LoadedArscEntry entry;
   ResTable_config config;
   uint32_t flags = 0u;
   ApkAssetsCookie cookie =
@@ -234,6 +332,10 @@
   const Res_value* device_value = reinterpret_cast<const Res_value*>(
       reinterpret_cast<const uint8_t*>(entry.entry) + dtohs(entry.entry->size));
   out_value->copyFrom_dtoh(*device_value);
+
+  // Convert the package ID to the runtime assigned package ID.
+  entry.dynamic_ref_table->lookupResourceValue(out_value);
+
   *out_selected_config = config;
   *out_flags = flags;
   return cookie;
@@ -247,7 +349,7 @@
     return cached_iter->second.get();
   }
 
-  LoadedArsc::Entry entry;
+  LoadedArscEntry entry;
   ResTable_config config;
   uint32_t flags = 0u;
   ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
@@ -270,8 +372,8 @@
       reinterpret_cast<const ResTable_map*>(reinterpret_cast<const uint8_t*>(map) + map->size);
   const ResTable_map* const map_entry_end = map_entry + dtohl(map->count);
 
-  const uint32_t parent = dtohl(map->parent.ident);
-  if (parent == 0) {
+  uint32_t parent_resid = dtohl(map->parent.ident);
+  if (parent_resid == 0) {
     // There is no parent, meaning there is nothing to inherit and we can do a simple
     // copy of the entries in the map.
     const size_t entry_count = map_entry_end - map_entry;
@@ -279,9 +381,18 @@
         malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))};
     ResolvedBag::Entry* new_entry = new_bag->entries;
     for (; map_entry != map_entry_end; ++map_entry) {
+      uint32_t new_key = dtohl(map_entry->name.ident);
+      if (!util::is_internal_resid(new_key)) {
+        // Attributes, arrays, etc don't have a resource id as the name. They specify
+        // other data, which would be wrong to change via a lookup.
+        if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
+          LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid);
+          return nullptr;
+        }
+      }
       new_entry->cookie = cookie;
       new_entry->value.copyFrom_dtoh(map_entry->value);
-      new_entry->key = dtohl(map_entry->name.ident);
+      new_entry->key = new_key;
       new_entry->key_pool = nullptr;
       new_entry->type_pool = nullptr;
       ++new_entry;
@@ -293,10 +404,14 @@
     return result;
   }
 
+  // In case the parent is a dynamic reference, resolve it.
+  entry.dynamic_ref_table->lookupResourceId(&parent_resid);
+
   // Get the parent and do a merge of the keys.
-  const ResolvedBag* parent_bag = GetBag(parent);
+  const ResolvedBag* parent_bag = GetBag(parent_resid);
   if (parent_bag == nullptr) {
     // Failed to get the parent that should exist.
+    LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, resid);
     return nullptr;
   }
 
@@ -315,7 +430,14 @@
 
   // The keys are expected to be in sorted order. Merge the two bags.
   while (map_entry != map_entry_end && parent_entry != parent_entry_end) {
-    const uint32_t child_key = dtohl(map_entry->name.ident);
+    uint32_t child_key = dtohl(map_entry->name.ident);
+    if (!util::is_internal_resid(child_key)) {
+      if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) {
+        LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid);
+        return nullptr;
+      }
+    }
+
     if (child_key <= parent_entry->key) {
       // Use the child key if it comes before the parent
       // or is equal to the parent (overrides).
@@ -340,9 +462,16 @@
 
   // Finish the child entries if they exist.
   while (map_entry != map_entry_end) {
+    uint32_t new_key = dtohl(map_entry->name.ident);
+    if (!util::is_internal_resid(new_key)) {
+      if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
+        LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid);
+        return nullptr;
+      }
+    }
     new_entry->cookie = cookie;
     new_entry->value.copyFrom_dtoh(map_entry->value);
-    new_entry->key = dtohl(map_entry->name.ident);
+    new_entry->key = new_key;
     new_entry->key_pool = nullptr;
     new_entry->type_pool = nullptr;
     ++map_entry;
@@ -511,12 +640,43 @@
     type_spec_flags |= entry.type_spec_flags;
 
     switch (entry.value.dataType) {
+      case Res_value::TYPE_NULL:
+        return kInvalidCookie;
+
       case Res_value::TYPE_ATTRIBUTE:
         resid = entry.value.data;
         break;
 
-      case Res_value::TYPE_NULL:
-        return kInvalidCookie;
+      case Res_value::TYPE_DYNAMIC_ATTRIBUTE: {
+        // Resolve the dynamic attribute to a normal attribute
+        // (with the right package ID).
+        resid = entry.value.data;
+        const DynamicRefTable* ref_table =
+            asset_manager_->GetDynamicRefTableForPackage(package_idx);
+        if (ref_table == nullptr || ref_table->lookupResourceId(&resid) != NO_ERROR) {
+          LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic attribute 0x%08x", resid);
+          return kInvalidCookie;
+        }
+      } break;
+
+      case Res_value::TYPE_DYNAMIC_REFERENCE: {
+        // Resolve the dynamic reference to a normal reference
+        // (with the right package ID).
+        out_value->dataType = Res_value::TYPE_REFERENCE;
+        out_value->data = entry.value.data;
+        const DynamicRefTable* ref_table =
+            asset_manager_->GetDynamicRefTableForPackage(package_idx);
+        if (ref_table == nullptr || ref_table->lookupResourceId(&out_value->data) != NO_ERROR) {
+          LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic reference 0x%08x",
+                                           out_value->data);
+          return kInvalidCookie;
+        }
+
+        if (out_flags != nullptr) {
+          *out_flags = type_spec_flags;
+        }
+        return entry.cookie;
+      }
 
       default:
         *out_value = entry.value;
@@ -550,14 +710,14 @@
 
   type_spec_flags_ = o.type_spec_flags_;
 
-  for (size_t p = 0; p < arraysize(packages_); p++) {
+  for (size_t p = 0; p < packages_.size(); p++) {
     const Package* package = o.packages_[p].get();
     if (package == nullptr) {
       packages_[p].reset();
       continue;
     }
 
-    for (size_t t = 0; t < arraysize(package->types); t++) {
+    for (size_t t = 0; t < package->types.size(); t++) {
       const Type* type = package->types[t].get();
       if (type == nullptr) {
         packages_[p]->types[t].reset();
diff --git a/libs/androidfw/ChunkIterator.cpp b/libs/androidfw/ChunkIterator.cpp
index 747aa4a..d8adbe5 100644
--- a/libs/androidfw/ChunkIterator.cpp
+++ b/libs/androidfw/ChunkIterator.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "Chunk.h"
+#include "androidfw/Chunk.h"
 
 #include "android-base/logging.h"
 
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 94d0d46..e17a3a6 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -32,15 +32,15 @@
 #endif
 #endif
 
-#include "Chunk.h"
 #include "androidfw/ByteBucketArray.h"
+#include "androidfw/Chunk.h"
 #include "androidfw/Util.h"
 
 using android::base::StringPrintf;
 
 namespace android {
 
-namespace {
+constexpr const static int kAppPackageId = 0x7f;
 
 // Element of a TypeSpec array. See TypeSpec.
 struct Type {
@@ -76,6 +76,8 @@
 // itself.
 using TypeSpecPtr = util::unique_cptr<TypeSpec>;
 
+namespace {
+
 // Builder that helps accumulate Type structs and then create a single
 // contiguous block of memory to store both the TypeSpec struct and
 // the Type structs.
@@ -110,37 +112,18 @@
 
 }  // namespace
 
-class LoadedPackage {
- public:
-  LoadedPackage() = default;
-
-  bool FindEntry(uint8_t type_id, uint16_t entry_id, const ResTable_config& config,
-                 LoadedArsc::Entry* out_entry, ResTable_config* out_selected_config,
-                 uint32_t* out_flags) const;
-
-  ResStringPool type_string_pool_;
-  ResStringPool key_string_pool_;
-  std::string package_name_;
-  int package_id_ = -1;
-
-  ByteBucketArray<TypeSpecPtr> type_specs_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
-};
-
-bool LoadedPackage::FindEntry(uint8_t type_id, uint16_t entry_id, const ResTable_config& config,
-                              LoadedArsc::Entry* out_entry, ResTable_config* out_selected_config,
+bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
+                              LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
                               uint32_t* out_flags) const {
-  ATRACE_NAME("LoadedPackage::FindEntry");
-  const TypeSpecPtr& ptr = type_specs_[type_id];
+  ATRACE_CALL();
+  const TypeSpecPtr& ptr = type_specs_[type_idx];
   if (ptr == nullptr) {
     return false;
   }
 
   // Don't bother checking if the entry ID is larger than
   // the number of entries.
-  if (entry_id >= dtohl(ptr->type_spec->entryCount)) {
+  if (entry_idx >= dtohl(ptr->type_spec->entryCount)) {
     return false;
   }
 
@@ -156,10 +139,10 @@
       // The configuration matches and is better than the previous selection.
       // Find the entry value if it exists for this configuration.
       size_t entry_count = dtohl(type->type->entryCount);
-      if (entry_id < entry_count) {
+      if (entry_idx < entry_count) {
         const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
             reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
-        const uint32_t offset = dtohl(entry_offsets[entry_id]);
+        const uint32_t offset = dtohl(entry_offsets[entry_idx]);
         if (offset != ResTable_type::NO_ENTRY) {
           // There is an entry for this resource, record it.
           best_config = &type->configuration;
@@ -175,7 +158,7 @@
   }
 
   const uint32_t* flags = reinterpret_cast<const uint32_t*>(ptr->type_spec + 1);
-  *out_flags = dtohl(flags[entry_id]);
+  *out_flags = dtohl(flags[entry_idx]);
   *out_selected_config = *best_config;
 
   const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
@@ -191,9 +174,10 @@
 // forward declarations and incomplete types.
 LoadedArsc::~LoadedArsc() {}
 
-bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config, Entry* out_entry,
-                           ResTable_config* out_selected_config, uint32_t* out_flags) const {
-  ATRACE_NAME("LoadedArsc::FindEntry");
+bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
+                           LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
+                           uint32_t* out_flags) const {
+  ATRACE_CALL();
   const uint8_t package_id = util::get_package_id(resid);
   const uint8_t type_id = util::get_type_id(resid);
   const uint16_t entry_id = util::get_entry_id(resid);
@@ -212,11 +196,11 @@
   return false;
 }
 
-const std::string* LoadedArsc::GetPackageNameForId(uint32_t resid) const {
+const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
   const uint8_t package_id = util::get_package_id(resid);
   for (const auto& loaded_package : packages_) {
     if (loaded_package->package_id_ == package_id) {
-      return &loaded_package->package_name_;
+      return loaded_package.get();
     }
   }
   return nullptr;
@@ -334,15 +318,24 @@
   return true;
 }
 
-static bool LoadPackage(const Chunk& chunk, LoadedPackage* loaded_package) {
+std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
   ATRACE_CALL();
+  std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()};
+
   const ResTable_package* header = chunk.header<ResTable_package>();
   if (header == nullptr) {
     LOG(ERROR) << "Chunk RES_TABLE_PACKAGE_TYPE is too small.";
-    return false;
+    return {};
   }
 
   loaded_package->package_id_ = dtohl(header->id);
+  if (loaded_package->package_id_ == 0) {
+    // Package ID of 0 means this is a shared library.
+    loaded_package->dynamic_ = true;
+  }
+
+  util::ReadUtf16StringFromDevice(header->name, arraysize(header->name),
+                                  &loaded_package->package_name_);
 
   // A TypeSpec builder. We use this to accumulate the set of Types
   // available for a TypeSpec, and later build a single, contiguous block
@@ -367,7 +360,7 @@
               child_chunk.header<ResStringPool_header>(), child_chunk.size());
           if (err != NO_ERROR) {
             LOG(ERROR) << "Corrupt package type string pool.";
-            return false;
+            return {};
           }
         } else if (pool_address == header_address + dtohl(header->keyStrings)) {
           // This string pool is the key string pool.
@@ -375,7 +368,7 @@
               child_chunk.header<ResStringPool_header>(), child_chunk.size());
           if (err != NO_ERROR) {
             LOG(ERROR) << "Corrupt package key string pool.";
-            return false;
+            return {};
           }
         } else {
           LOG(WARNING) << "Too many string pool chunks found in package.";
@@ -390,7 +383,7 @@
           TypeSpecPtr type_spec_ptr = types_builder->Build();
           if (type_spec_ptr == nullptr) {
             LOG(ERROR) << "Too many type configurations, overflow detected.";
-            return false;
+            return {};
           }
 
           loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
@@ -402,12 +395,12 @@
         const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>();
         if (type_spec == nullptr) {
           LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE is too small.";
-          return false;
+          return {};
         }
 
         if (type_spec->id == 0) {
           LOG(ERROR) << "Chunk RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0.";
-          return false;
+          return {};
         }
 
         // The data portion of this chunk contains entry_count 32bit entries,
@@ -419,12 +412,12 @@
         // space for entries (EEEE) in the resource ID 0xPPTTEEEE.
         if (entry_count > std::numeric_limits<uint16_t>::max()) {
           LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_SPEC_TYPE: " << entry_count << ".";
-          return false;
+          return {};
         }
 
         if (entry_count * sizeof(uint32_t) > chunk.data_size()) {
           LOG(ERROR) << "Chunk too small to hold entries in RES_TABLE_TYPE_SPEC_TYPE.";
-          return false;
+          return {};
         }
 
         last_type_idx = type_spec->id - 1;
@@ -435,28 +428,63 @@
         const ResTable_type* type = child_chunk.header<ResTable_type>();
         if (type == nullptr) {
           LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE is too small.";
-          return false;
+          return {};
         }
 
         if (type->id == 0) {
           LOG(ERROR) << "Chunk RES_TABLE_TYPE_TYPE has invalid ID 0.";
-          return false;
+          return {};
         }
 
         // Type chunks must be preceded by their TypeSpec chunks.
         if (!types_builder || type->id - 1 != last_type_idx) {
           LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without "
                         "RES_TABLE_TYPE_SPEC_TYPE.";
-          return false;
+          return {};
         }
 
         if (!VerifyType(child_chunk)) {
-          return false;
+          return {};
         }
 
         types_builder->AddType(type);
       } break;
 
+      case RES_TABLE_LIBRARY_TYPE: {
+        const ResTable_lib_header* lib = child_chunk.header<ResTable_lib_header>();
+        if (lib == nullptr) {
+          LOG(ERROR) << "Chunk RES_TABLE_LIBRARY_TYPE is too small.";
+          return {};
+        }
+
+        if (child_chunk.data_size() / sizeof(ResTable_lib_entry) < dtohl(lib->count)) {
+          LOG(ERROR) << "Chunk too small to hold entries in RES_TABLE_LIBRARY_TYPE.";
+          return {};
+        }
+
+        loaded_package->dynamic_package_map_.reserve(dtohl(lib->count));
+
+        const ResTable_lib_entry* const entry_begin =
+            reinterpret_cast<const ResTable_lib_entry*>(child_chunk.data_ptr());
+        const ResTable_lib_entry* const entry_end = entry_begin + dtohl(lib->count);
+        for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
+          std::string package_name;
+          util::ReadUtf16StringFromDevice(entry_iter->packageName,
+                                          arraysize(entry_iter->packageName), &package_name);
+
+          if (dtohl(entry_iter->packageId) >= std::numeric_limits<uint8_t>::max()) {
+            LOG(ERROR) << base::StringPrintf(
+                "Package ID %02x in RES_TABLE_LIBRARY_TYPE too large for package '%s'.",
+                dtohl(entry_iter->packageId), package_name.c_str());
+            return {};
+          }
+
+          loaded_package->dynamic_package_map_.emplace_back(std::move(package_name),
+                                                            dtohl(entry_iter->packageId));
+        }
+
+      } break;
+
       default:
         LOG(WARNING) << base::StringPrintf("Unknown chunk type '%02x'.", chunk.type());
         break;
@@ -468,19 +496,19 @@
     TypeSpecPtr type_spec_ptr = types_builder->Build();
     if (type_spec_ptr == nullptr) {
       LOG(ERROR) << "Too many type configurations, overflow detected.";
-      return false;
+      return {};
     }
     loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
   }
 
   if (iter.HadError()) {
     LOG(ERROR) << iter.GetLastError();
-    return false;
+    return {};
   }
-  return true;
+  return loaded_package;
 }
 
-bool LoadedArsc::LoadTable(const Chunk& chunk) {
+bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) {
   ATRACE_CALL();
   const ResTable_header* header = chunk.header<ResTable_header>();
   if (header == nullptr) {
@@ -520,10 +548,15 @@
         }
         packages_seen++;
 
-        std::unique_ptr<LoadedPackage> loaded_package = util::make_unique<LoadedPackage>();
-        if (!LoadPackage(child_chunk, loaded_package.get())) {
+        std::unique_ptr<LoadedPackage> loaded_package = LoadedPackage::Load(child_chunk);
+        if (!loaded_package) {
           return false;
         }
+
+        // Mark the package as dynamic if we are forcefully loading the Apk as a shared library.
+        if (loaded_package->package_id_ == kAppPackageId) {
+          loaded_package->dynamic_ = load_as_shared_library;
+        }
         packages_.push_back(std::move(loaded_package));
       } break;
 
@@ -540,7 +573,8 @@
   return true;
 }
 
-std::unique_ptr<LoadedArsc> LoadedArsc::Load(const void* data, size_t len) {
+std::unique_ptr<LoadedArsc> LoadedArsc::Load(const void* data, size_t len,
+                                             bool load_as_shared_library) {
   ATRACE_CALL();
 
   // Not using make_unique because the constructor is private.
@@ -551,7 +585,7 @@
     const Chunk chunk = iter.Next();
     switch (chunk.type()) {
       case RES_TABLE_TYPE:
-        if (!loaded_arsc->LoadTable(chunk)) {
+        if (!loaded_arsc->LoadTable(chunk, load_as_shared_library)) {
           return {};
         }
         break;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
new file mode 100644
index 0000000..202bc8e
--- /dev/null
+++ b/libs/androidfw/Util.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include "androidfw/Util.h"
+
+#include <string>
+
+#include "utils/ByteOrder.h"
+#include "utils/Unicode.h"
+
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+namespace android {
+namespace util {
+
+void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) {
+  char buf[5];
+  while (*src && len != 0) {
+    char16_t c = static_cast<char16_t>(dtohs(*src));
+    utf16_to_utf8(&c, 1, buf, sizeof(buf));
+    out->append(buf, strlen(buf));
+    ++src;
+    --len;
+  }
+}
+
+} // namespace util
+} // namespace android
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index a3d67f0..9d4fd29 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -32,6 +32,7 @@
 class ApkAssets {
  public:
   static std::unique_ptr<ApkAssets> Load(const std::string& path);
+  static std::unique_ptr<ApkAssets> LoadAsSharedLibrary(const std::string& path);
 
   std::unique_ptr<Asset> Open(const std::string& path,
                               Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
@@ -43,6 +44,8 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(ApkAssets);
 
+  static std::unique_ptr<ApkAssets> LoadImpl(const std::string& path, bool load_as_shared_library);
+
   ApkAssets() = default;
 
   struct ZipArchivePtrCloser {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 66d5034..8655339 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -19,6 +19,7 @@
 
 #include "android-base/macros.h"
 
+#include <array>
 #include <limits>
 #include <unordered_map>
 
@@ -95,18 +96,21 @@
   // new resource IDs.
   bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
 
-  const std::vector<const ApkAssets*> GetApkAssets() const;
+  inline const std::vector<const ApkAssets*> GetApkAssets() const { return apk_assets_; }
 
   // Returns the string pool for the given asset cookie.
   // Use the string pool returned here with a valid Res_value object of
   // type Res_value::TYPE_STRING.
   const ResStringPool* GetStringPoolForCookie(ApkAssetsCookie cookie) const;
 
+  // Returns the DynamicRefTable for the given package ID.
+  const DynamicRefTable* GetDynamicRefTableForPackage(uint32_t package_id) const;
+
   // Sets/resets the configuration for this AssetManager. This will cause all
   // caches that are related to the configuration change to be invalidated.
   void SetConfiguration(const ResTable_config& configuration);
 
-  const ResTable_config& GetConfiguration() const;
+  inline const ResTable_config& GetConfiguration() const { return configuration_; }
 
   // Searches the set of APKs loaded by this AssetManager and opens the first one found located
   // in the assets/ directory.
@@ -173,6 +177,8 @@
   // Creates a new Theme from this AssetManager.
   std::unique_ptr<Theme> NewTheme();
 
+  void DumpToLog() const;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(AssetManager2);
 
@@ -189,9 +195,13 @@
   // `out_flags` stores the resulting bitmask of configuration axis with which the resource
   // value varies.
   ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match,
-                            LoadedArsc::Entry* out_entry, ResTable_config* out_selected_config,
+                            LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
                             uint32_t* out_flags);
 
+  // Assigns package IDs to all shared library ApkAssets.
+  // Should be called whenever the ApkAssets are changed.
+  void BuildDynamicRefTable();
+
   // Purge all resources that are cached and vary by the configuration axis denoted by the
   // bitmask `diff`.
   void InvalidateCaches(uint32_t diff);
@@ -200,6 +210,22 @@
   // have a longer lifetime.
   std::vector<const ApkAssets*> apk_assets_;
 
+  struct PackageGroup {
+    std::vector<const LoadedPackage*> packages_;
+    std::vector<ApkAssetsCookie> cookies_;
+    DynamicRefTable dynamic_ref_table;
+  };
+
+  // DynamicRefTables for shared library package resolution.
+  // These are ordered according to apk_assets_. The mappings may change depending on what is
+  // in apk_assets_, therefore they must be stored in the AssetManager and not in the
+  // immutable ApkAssets class.
+  std::vector<PackageGroup> package_groups_;
+
+  // An array mapping package ID to index into package_groups. This keeps the lookup fast
+  // without taking too much memory.
+  std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
+
   // The current configuration set for this AssetManager. When this changes, cached resources
   // may need to be purged.
   ResTable_config configuration_;
@@ -279,12 +305,12 @@
   struct Package {
     // Each element of Type will be a dynamically sized object
     // allocated to have the entries stored contiguously with the Type.
-    util::unique_cptr<Type> types[kTypeCount];
+    std::array<util::unique_cptr<Type>, kTypeCount> types;
   };
 
   AssetManager2* asset_manager_;
   uint32_t type_spec_flags_ = 0u;
-  std::unique_ptr<Package> packages_[kPackageCount];
+  std::array<std::unique_ptr<Package>, kPackageCount> packages_;
 };
 
 inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) { return bag->entries; }
diff --git a/libs/androidfw/Chunk.h b/libs/androidfw/include/androidfw/Chunk.h
similarity index 100%
rename from libs/androidfw/Chunk.h
rename to libs/androidfw/include/androidfw/Chunk.h
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index e2e56c8..8362008 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -22,12 +22,82 @@
 
 #include "android-base/macros.h"
 
+#include "androidfw/ByteBucketArray.h"
+#include "androidfw/Chunk.h"
 #include "androidfw/ResourceTypes.h"
+#include "androidfw/Util.h"
 
 namespace android {
 
-class Chunk;
-class LoadedPackage;
+class DynamicPackageEntry {
+ public:
+  DynamicPackageEntry() = default;
+  DynamicPackageEntry(std::string&& package_name, int package_id)
+      : package_name(std::move(package_name)), package_id(package_id) {}
+
+  std::string package_name;
+  int package_id = 0;
+};
+
+struct LoadedArscEntry {
+  // A pointer to the resource table entry for this resource.
+  // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
+  // a ResTable_map_entry and processed as a bag/map.
+  const ResTable_entry* entry = nullptr;
+
+  // The dynamic package ID map for the package from which this resource came from.
+  const DynamicRefTable* dynamic_ref_table = nullptr;
+
+  // The string pool reference to the type's name. This uses a different string pool than
+  // the global string pool, but this is hidden from the caller.
+  StringPoolRef type_string_ref;
+
+  // The string pool reference to the entry's name. This uses a different string pool than
+  // the global string pool, but this is hidden from the caller.
+  StringPoolRef entry_string_ref;
+};
+
+struct TypeSpec;
+class LoadedArsc;
+
+class LoadedPackage {
+  friend class LoadedArsc;
+
+ public:
+  bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
+                 LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
+                 uint32_t* out_flags) const;
+
+  inline const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; }
+
+  inline const ResStringPool* GetKeyStringPool() const { return &key_string_pool_; }
+
+  inline const std::string& GetPackageName() const { return package_name_; }
+
+  inline int GetPackageId() const { return package_id_; }
+
+  inline bool IsDynamic() const { return dynamic_; }
+
+  inline const std::vector<DynamicPackageEntry>& GetDynamicPackageMap() const {
+    return dynamic_package_map_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
+
+  static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk);
+
+  LoadedPackage() = default;
+
+  ResStringPool type_string_pool_;
+  ResStringPool key_string_pool_;
+  std::string package_name_;
+  int package_id_ = -1;
+  bool dynamic_ = false;
+
+  ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
+  std::vector<DynamicPackageEntry> dynamic_package_map_;
+};
 
 // Read-only view into a resource table. This class validates all data
 // when loading, including offsets and lengths.
@@ -35,7 +105,8 @@
  public:
   // Load the resource table from memory. The data's lifetime must out-live the
   // object returned from this method.
-  static std::unique_ptr<LoadedArsc> Load(const void* data, size_t len);
+  static std::unique_ptr<LoadedArsc> Load(const void* data, size_t len,
+                                          bool load_as_shared_library = false);
 
   ~LoadedArsc();
 
@@ -43,39 +114,28 @@
   // (Res_value::dataType == Res_value::TYPE_STRING) are indexed.
   inline const ResStringPool* GetStringPool() const { return &global_string_pool_; }
 
-  struct Entry {
-    // A pointer to the resource table entry for this resource.
-    // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
-    // a ResTable_map_entry and processed as a bag/map.
-    const ResTable_entry* entry = nullptr;
-
-    // The string pool reference to the type's name. This uses a different string pool than
-    // the global string pool, but this is hidden from the caller.
-    StringPoolRef type_string_ref;
-
-    // The string pool reference to the entry's name. This uses a different string pool than
-    // the global string pool, but this is hidden from the caller.
-    StringPoolRef entry_string_ref;
-  };
-
   // Finds the resource with ID `resid` with the best value for configuration `config`.
   // The parameter `out_entry` will be filled with the resulting resource entry.
   // The resource entry can be a simple entry (ResTable_entry) or a complex bag
   // (ResTable_entry_map).
-  bool FindEntry(uint32_t resid, const ResTable_config& config, Entry* out_entry,
+  bool FindEntry(uint32_t resid, const ResTable_config& config, LoadedArscEntry* out_entry,
                  ResTable_config* selected_config, uint32_t* out_flags) const;
 
   // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist.
-  const std::string* GetPackageNameForId(uint32_t resid) const;
+  const LoadedPackage* GetPackageForId(uint32_t resid) const;
+
+  inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const {
+    return packages_;
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
 
   LoadedArsc() = default;
-  bool LoadTable(const Chunk& chunk);
+  bool LoadTable(const Chunk& chunk, bool load_as_shared_library);
 
   ResStringPool global_string_pool_;
-  std::vector<std::unique_ptr<LoadedPackage>> packages_;
+  std::vector<std::unique_ptr<const LoadedPackage>> packages_;
 };
 
 }  // namespace android
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 86ab123..56c22e6 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1538,6 +1538,8 @@
     uint16_t packageName[128];
 };
 
+class AssetManager2;
+
 /**
  * Holds the shared library ID table. Shared libraries are assigned package IDs at
  * build time, but they may be loaded in a different order, so we need to maintain
@@ -1548,7 +1550,9 @@
  */
 class DynamicRefTable
 {
+    friend class AssetManager2;
 public:
+    DynamicRefTable() = default;
     DynamicRefTable(uint8_t packageId, bool appAsLib);
 
     // Loads an unmapped reference table from the package.
@@ -1563,18 +1567,18 @@
 
     // Performs the actual conversion of build-time resource ID to run-time
     // resource ID.
-    inline status_t lookupResourceId(uint32_t* resId) const;
-    inline status_t lookupResourceValue(Res_value* value) const;
+    status_t lookupResourceId(uint32_t* resId) const;
+    status_t lookupResourceValue(Res_value* value) const;
 
     inline const KeyedVector<String16, uint8_t>& entries() const {
         return mEntries;
     }
 
 private:
-    const uint8_t                   mAssignedPackageId;
+    uint8_t                         mAssignedPackageId = 0;
     uint8_t                         mLookupTable[256];
     KeyedVector<String16, uint8_t>  mEntries;
-    bool                            mAppAsLib;
+    bool                            mAppAsLib = false;
 };
 
 bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index 5266d09..fd96730 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -102,6 +102,10 @@
   pointer ptr_;
 };
 
+inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) {
+  return resid | (static_cast<uint32_t>(package_id) << 24);
+}
+
 inline uint8_t get_package_id(uint32_t resid) {
   return static_cast<uint8_t>((resid >> 24) & 0x000000ffu);
 }
@@ -113,7 +117,7 @@
 
 inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); }
 
-inline bool is_internal_id(uint32_t resid) {
+inline bool is_internal_resid(uint32_t resid) {
   return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0;
 }
 
@@ -121,6 +125,8 @@
   return (resid & 0x00ff0000u) != 0 && (resid & 0xff000000u) != 0;
 }
 
+void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
+
 }  // namespace util
 }  // namespace android
 
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 3a1fc8f..0203712 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -26,9 +26,28 @@
 TEST(ApkAssetsTest, LoadApk) {
   std::unique_ptr<ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
   ASSERT_NE(nullptr, loaded_apk);
+  EXPECT_NE(nullptr, loaded_apk->GetLoadedArsc());
 
   std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
   ASSERT_NE(nullptr, asset);
 }
 
+TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
+  std::unique_ptr<ApkAssets> loaded_apk =
+      ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
+  ASSERT_NE(nullptr, loaded_apk);
+  const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
+  ASSERT_NE(nullptr, loaded_arsc);
+  ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
+  EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic());
+
+  loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk");
+  ASSERT_NE(nullptr, loaded_apk);
+
+  loaded_arsc = loaded_apk->GetLoadedArsc();
+  ASSERT_NE(nullptr, loaded_arsc);
+  ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
+  EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 9ff9478..b3c2dc3 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -16,6 +16,7 @@
 
 #include "benchmark/benchmark.h"
 
+#include "android-base/stringprintf.h"
 #include "androidfw/ApkAssets.h"
 #include "androidfw/AssetManager.h"
 #include "androidfw/AssetManager2.h"
@@ -23,10 +24,12 @@
 
 #include "TestHelpers.h"
 #include "data/basic/R.h"
+#include "data/libclient/R.h"
 #include "data/styles/R.h"
 
-namespace basic = com::android::basic;
 namespace app = com::android::app;
+namespace basic = com::android::basic;
+namespace libclient = com::android::libclient;
 
 namespace android {
 
@@ -78,101 +81,108 @@
 }
 BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld);
 
-static void BM_AssetManagerGetResource(benchmark::State& state) {
-  std::unique_ptr<ApkAssets> apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
-  if (apk == nullptr) {
-    state.SkipWithError("Failed to load assets");
-    return;
+static void GetResourceBenchmark(const std::vector<std::string>& paths,
+                                 const ResTable_config* config, uint32_t resid,
+                                 benchmark::State& state) {
+  std::vector<std::unique_ptr<ApkAssets>> apk_assets;
+  std::vector<const ApkAssets*> apk_assets_ptrs;
+  for (const std::string& path : paths) {
+    std::unique_ptr<ApkAssets> apk = ApkAssets::Load(path);
+    if (apk == nullptr) {
+      state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
+      return;
+    }
+    apk_assets_ptrs.push_back(apk.get());
+    apk_assets.push_back(std::move(apk));
   }
 
-  AssetManager2 assets;
-  assets.SetApkAssets({apk.get()});
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets(apk_assets_ptrs);
+  if (config != nullptr) {
+    assetmanager.SetConfiguration(*config);
+  }
 
   Res_value value;
   ResTable_config selected_config;
   uint32_t flags;
 
   while (state.KeepRunning()) {
-    assets.GetResource(basic::R::integer::number1, false /* may_be_bag */,
-                       0u /* density_override */, &value, &selected_config, &flags);
+    assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value,
+                             &selected_config, &flags);
   }
 }
+
+static void GetResourceBenchmarkOld(const std::vector<std::string>& paths,
+                                    const ResTable_config* config, uint32_t resid,
+                                    benchmark::State& state) {
+  AssetManager assetmanager;
+  for (const std::string& path : paths) {
+    if (!assetmanager.addAssetPath(String8(path.c_str()), nullptr /* cookie */,
+                                   false /* appAsLib */, false /* isSystemAssets */)) {
+      state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
+      return;
+    }
+  }
+
+  if (config != nullptr) {
+    assetmanager.setConfiguration(*config);
+  }
+
+  const ResTable& table = assetmanager.getResources(true);
+
+  Res_value value;
+  ResTable_config selected_config;
+  uint32_t flags;
+
+  while (state.KeepRunning()) {
+    table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
+                      &selected_config);
+  }
+}
+
+static void BM_AssetManagerGetResource(benchmark::State& state) {
+  GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
+                       basic::R::integer::number1, state);
+}
 BENCHMARK(BM_AssetManagerGetResource);
 
 static void BM_AssetManagerGetResourceOld(benchmark::State& state) {
-  AssetManager assets;
-  if (!assets.addAssetPath(String8((GetTestDataPath() + "/basic/basic.apk").data()),
-                           nullptr /* cookie */, false /* appAsLib */,
-                           false /* isSystemAssets */)) {
-    state.SkipWithError("Failed to load assets");
-    return;
-  }
-
-  const ResTable& table = assets.getResources(true);
-
-  Res_value value;
-  ResTable_config selected_config;
-  uint32_t flags;
-
-  while (state.KeepRunning()) {
-    table.getResource(basic::R::integer::number1, &value, false /* may_be_bag */,
-                      0u /* density_override */, &flags, &selected_config);
-  }
+  GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
+                          basic::R::integer::number1, state);
 }
 BENCHMARK(BM_AssetManagerGetResourceOld);
 
+static void BM_AssetManagerGetLibraryResource(benchmark::State& state) {
+  GetResourceBenchmark(
+      {GetTestDataPath() + "/lib_two/lib_two.apk", GetTestDataPath() + "/lib_one/lib_one.apk",
+       GetTestDataPath() + "/libclient/libclient.apk"},
+      nullptr /*config*/, libclient::R::string::foo_one, state);
+}
+BENCHMARK(BM_AssetManagerGetLibraryResource);
+
+static void BM_AssetManagerGetLibraryResourceOld(benchmark::State& state) {
+  GetResourceBenchmarkOld(
+      {GetTestDataPath() + "/lib_two/lib_two.apk", GetTestDataPath() + "/lib_one/lib_one.apk",
+       GetTestDataPath() + "/libclient/libclient.apk"},
+      nullptr /*config*/, libclient::R::string::foo_one, state);
+}
+BENCHMARK(BM_AssetManagerGetLibraryResourceOld);
+
 constexpr static const uint32_t kStringOkId = 0x0104000au;
 
 static void BM_AssetManagerGetResourceFrameworkLocale(benchmark::State& state) {
-  std::unique_ptr<ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
-  if (apk == nullptr) {
-    state.SkipWithError("Failed to load assets");
-    return;
-  }
-
-  AssetManager2 assets;
-  assets.SetApkAssets({apk.get()});
-
   ResTable_config config;
   memset(&config, 0, sizeof(config));
   memcpy(config.language, "fr", 2);
-  assets.SetConfiguration(config);
-
-  Res_value value;
-  ResTable_config selected_config;
-  uint32_t flags;
-
-  while (state.KeepRunning()) {
-    assets.GetResource(kStringOkId, false /* may_be_bag */, 0u /* density_override */, &value,
-                       &selected_config, &flags);
-  }
+  GetResourceBenchmark({kFrameworkPath}, &config, kStringOkId, state);
 }
 BENCHMARK(BM_AssetManagerGetResourceFrameworkLocale);
 
 static void BM_AssetManagerGetResourceFrameworkLocaleOld(benchmark::State& state) {
-  AssetManager assets;
-  if (!assets.addAssetPath(String8((GetTestDataPath() + "/basic/basic.apk").data()),
-                           nullptr /* cookie */, false /* appAsLib */,
-                           false /* isSystemAssets */)) {
-    state.SkipWithError("Failed to load assets");
-    return;
-  }
-
   ResTable_config config;
   memset(&config, 0, sizeof(config));
   memcpy(config.language, "fr", 2);
-  assets.setConfiguration(config, nullptr);
-
-  const ResTable& table = assets.getResources(true);
-
-  Res_value value;
-  ResTable_config selected_config;
-  uint32_t flags;
-
-  while (state.KeepRunning()) {
-    table.getResource(kStringOkId, &value, false /* may_be_bag */, 0u /* density_override */,
-                      &flags, &selected_config);
-  }
+  GetResourceBenchmarkOld({kFrameworkPath}, &config, kStringOkId, state);
 }
 BENCHMARK(BM_AssetManagerGetResourceFrameworkLocaleOld);
 
@@ -202,8 +212,7 @@
 static void BM_AssetManagerGetBagOld(benchmark::State& state) {
   AssetManager assets;
   if (!assets.addAssetPath(String8((GetTestDataPath() + "/styles/styles.apk").data()),
-                           nullptr /* cookie */, false /* appAsLib */,
-                           false /* isSystemAssets */)) {
+                           nullptr /*cookie*/, false /*appAsLib*/, false /*isSystemAssets*/)) {
     state.SkipWithError("Failed to load assets");
     return;
   }
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 39c5381..543456a 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -20,11 +20,19 @@
 #include "android-base/logging.h"
 
 #include "TestHelpers.h"
+#include "data/appaslib/R.h"
 #include "data/basic/R.h"
+#include "data/lib_one/R.h"
+#include "data/lib_two/R.h"
+#include "data/libclient/R.h"
 #include "data/styles/R.h"
 
-namespace basic = com::android::basic;
 namespace app = com::android::app;
+namespace appaslib = com::android::appaslib::app;
+namespace basic = com::android::basic;
+namespace lib_one = com::android::lib_one;
+namespace lib_two = com::android::lib_two;
+namespace libclient = com::android::libclient;
 
 namespace android {
 
@@ -39,15 +47,31 @@
 
     style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
     ASSERT_NE(nullptr, style_assets_);
+
+    lib_one_assets_ = ApkAssets::Load(GetTestDataPath() + "/lib_one/lib_one.apk");
+    ASSERT_NE(nullptr, lib_one_assets_);
+
+    lib_two_assets_ = ApkAssets::Load(GetTestDataPath() + "/lib_two/lib_two.apk");
+    ASSERT_NE(nullptr, lib_two_assets_);
+
+    libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk");
+    ASSERT_NE(nullptr, libclient_assets_);
+
+    appaslib_assets_ = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
+    ASSERT_NE(nullptr, appaslib_assets_);
   }
 
  protected:
   std::unique_ptr<ApkAssets> basic_assets_;
   std::unique_ptr<ApkAssets> basic_de_fr_assets_;
   std::unique_ptr<ApkAssets> style_assets_;
+  std::unique_ptr<ApkAssets> lib_one_assets_;
+  std::unique_ptr<ApkAssets> lib_two_assets_;
+  std::unique_ptr<ApkAssets> libclient_assets_;
+  std::unique_ptr<ApkAssets> appaslib_assets_;
 };
 
-TEST_F(AssetManager2Test, FindsResourcesFromSingleApkAssets) {
+TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) {
   ResTable_config desired_config;
   memset(&desired_config, 0, sizeof(desired_config));
   desired_config.language[0] = 'd';
@@ -77,7 +101,7 @@
   EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
 }
 
-TEST_F(AssetManager2Test, FindsResourcesFromMultipleApkAssets) {
+TEST_F(AssetManager2Test, FindsResourceFromMultipleApkAssets) {
   ResTable_config desired_config;
   memset(&desired_config, 0, sizeof(desired_config));
   desired_config.language[0] = 'd';
@@ -99,7 +123,7 @@
   // Came from our de_fr ApkAssets.
   EXPECT_EQ(1, cookie);
 
-  // The configuration is german.
+  // The configuration is German.
   EXPECT_EQ('d', selected_config.language[0]);
   EXPECT_EQ('e', selected_config.language[1]);
 
@@ -107,7 +131,72 @@
   EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
 }
 
-TEST_F(AssetManager2Test, FindsBagResourcesFromSingleApkAssets) {
+TEST_F(AssetManager2Test, FindsResourceFromSharedLibrary) {
+  AssetManager2 assetmanager;
+
+  // libclient is built with lib_one and then lib_two in order.
+  // Reverse the order to test that proper package ID re-assignment is happening.
+  assetmanager.SetApkAssets(
+      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+  Res_value value;
+  ResTable_config selected_config;
+  uint32_t flags;
+
+  ApkAssetsCookie cookie =
+      assetmanager.GetResource(libclient::R::string::foo_one, false /*may_be_bag*/,
+                               0 /*density_override*/, &value, &selected_config, &flags);
+  ASSERT_NE(kInvalidCookie, cookie);
+
+  // Reference comes from libclient.
+  EXPECT_EQ(2, cookie);
+  EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
+
+  // Lookup the reference.
+  cookie = assetmanager.GetResource(value.data, false /* may_be_bag */, 0 /* density_override*/,
+                                    &value, &selected_config, &flags);
+  ASSERT_NE(kInvalidCookie, cookie);
+  EXPECT_EQ(1, cookie);
+  EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
+  EXPECT_EQ(std::string("Foo from lib_one"),
+            GetStringFromPool(assetmanager.GetStringPoolForCookie(cookie), value.data));
+
+  cookie = assetmanager.GetResource(libclient::R::string::foo_two, false /*may_be_bag*/,
+                                    0 /*density_override*/, &value, &selected_config, &flags);
+  ASSERT_NE(kInvalidCookie, cookie);
+
+  // Reference comes from libclient.
+  EXPECT_EQ(2, cookie);
+  EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
+
+  // Lookup the reference.
+  cookie = assetmanager.GetResource(value.data, false /* may_be_bag */, 0 /* density_override*/,
+                                    &value, &selected_config, &flags);
+  ASSERT_NE(kInvalidCookie, cookie);
+  EXPECT_EQ(0, cookie);
+  EXPECT_EQ(Res_value::TYPE_STRING, value.dataType);
+  EXPECT_EQ(std::string("Foo from lib_two"),
+            GetStringFromPool(assetmanager.GetStringPoolForCookie(cookie), value.data));
+}
+
+TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({appaslib_assets_.get()});
+
+  // The appaslib package will have been assigned the package ID 0x02.
+
+  Res_value value;
+  ResTable_config selected_config;
+  uint32_t flags;
+  ApkAssetsCookie cookie = assetmanager.GetResource(
+      util::fix_package_id(appaslib::R::integer::number1, 0x02), false /*may_be_bag*/,
+      0u /*density_override*/, &value, &selected_config, &flags);
+  ASSERT_NE(kInvalidCookie, cookie);
+  EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
+  EXPECT_EQ(util::fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
+}
+
+TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({basic_assets_.get()});
 
@@ -128,6 +217,27 @@
   EXPECT_EQ(0, bag->entries[2].cookie);
 }
 
+TEST_F(AssetManager2Test, FindsBagResourceFromMultipleApkAssets) {}
+
+TEST_F(AssetManager2Test, FindsBagResourceFromSharedLibrary) {
+  AssetManager2 assetmanager;
+
+  // libclient is built with lib_one and then lib_two in order.
+  // Reverse the order to test that proper package ID re-assignment is happening.
+  assetmanager.SetApkAssets(
+      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+  const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::Theme);
+  ASSERT_NE(nullptr, bag);
+  ASSERT_GE(bag->entry_count, 2u);
+
+  // First two attributes come from lib_one.
+  EXPECT_EQ(1, bag->entries[0].cookie);
+  EXPECT_EQ(0x03, util::get_package_id(bag->entries[0].key));
+  EXPECT_EQ(1, bag->entries[1].cookie);
+  EXPECT_EQ(0x03, util::get_package_id(bag->entries[1].key));
+}
+
 TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({style_assets_.get()});
@@ -181,8 +291,6 @@
   EXPECT_EQ(0, bag_two->entries[4].cookie);
 }
 
-TEST_F(AssetManager2Test, FindsBagResourcesFromMultipleApkAssets) {}
-
 TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) {}
 
 TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {}
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 47b3894..045507e 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -16,21 +16,18 @@
 
 #include "androidfw/LoadedArsc.h"
 
-#include "android-base/file.h"
-#include "android-base/logging.h"
-#include "android-base/macros.h"
-
 #include "TestHelpers.h"
 #include "data/basic/R.h"
+#include "data/libclient/R.h"
 #include "data/styles/R.h"
 
 namespace app = com::android::app;
 namespace basic = com::android::basic;
+namespace libclient = com::android::libclient;
 
 namespace android {
 
 TEST(LoadedArscTest, LoadSinglePackageArsc) {
-  base::ScopedLogSeverity _log(base::LogSeverity::DEBUG);
   std::string contents;
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", "resources.arsc",
                                       &contents));
@@ -38,11 +35,16 @@
   std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size());
   ASSERT_NE(nullptr, loaded_arsc);
 
+  const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
+  ASSERT_EQ(1u, packages.size());
+  EXPECT_EQ(std::string("com.android.app"), packages[0]->GetPackageName());
+  EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+
   ResTable_config config;
   memset(&config, 0, sizeof(config));
   config.sdkVersion = 24;
 
-  LoadedArsc::Entry entry;
+  LoadedArscEntry entry;
   ResTable_config selected_config;
   uint32_t flags;
 
@@ -52,7 +54,6 @@
 }
 
 TEST(LoadedArscTest, FindDefaultEntry) {
-  base::ScopedLogSeverity _log(base::LogSeverity::DEBUG);
   std::string contents;
   ASSERT_TRUE(
       ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
@@ -65,7 +66,7 @@
   desired_config.language[0] = 'd';
   desired_config.language[1] = 'e';
 
-  LoadedArsc::Entry entry;
+  LoadedArscEntry entry;
   ResTable_config selected_config;
   uint32_t flags;
 
@@ -74,6 +75,70 @@
   ASSERT_NE(nullptr, entry.entry);
 }
 
+TEST(LoadedArscTest, LoadSharedLibrary) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
+                                      &contents));
+
+  std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size());
+  ASSERT_NE(nullptr, loaded_arsc);
+
+  const auto& packages = loaded_arsc->GetPackages();
+  ASSERT_EQ(1u, packages.size());
+
+  EXPECT_TRUE(packages[0]->IsDynamic());
+  EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName());
+  EXPECT_EQ(0, packages[0]->GetPackageId());
+
+  const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
+
+  // The library has no dependencies.
+  ASSERT_TRUE(dynamic_pkg_map.empty());
+}
+
+TEST(LoadedArscTest, LoadAppLinkedAgainstSharedLibrary) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/libclient/libclient.apk",
+                                      "resources.arsc", &contents));
+
+  std::unique_ptr<LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(), contents.size());
+  ASSERT_NE(nullptr, loaded_arsc);
+
+  const auto& packages = loaded_arsc->GetPackages();
+  ASSERT_EQ(1u, packages.size());
+
+  EXPECT_FALSE(packages[0]->IsDynamic());
+  EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName());
+  EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+
+  const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
+
+  // The library has two dependencies.
+  ASSERT_EQ(2u, dynamic_pkg_map.size());
+
+  EXPECT_EQ(std::string("com.android.lib_one"), dynamic_pkg_map[0].package_name);
+  EXPECT_EQ(0x02, dynamic_pkg_map[0].package_id);
+
+  EXPECT_EQ(std::string("com.android.lib_two"), dynamic_pkg_map[1].package_name);
+  EXPECT_EQ(0x03, dynamic_pkg_map[1].package_id);
+}
+
+TEST(LoadedArscTest, LoadAppAsSharedLibrary) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/appaslib/appaslib.apk",
+                                      "resources.arsc", &contents));
+
+  std::unique_ptr<LoadedArsc> loaded_arsc =
+      LoadedArsc::Load(contents.data(), contents.size(), true /*load_as_shared_library*/);
+  ASSERT_NE(nullptr, loaded_arsc);
+
+  const auto& packages = loaded_arsc->GetPackages();
+  ASSERT_EQ(1u, packages.size());
+
+  EXPECT_TRUE(packages[0]->IsDynamic());
+  EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+}
+
 // structs with size fields (like Res_value, ResTable_entry) should be
 // backwards and forwards compatible (aka checking the size field against
 // sizeof(Res_value) might not be backwards compatible.
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
index b151f3f..ad1cd2b 100644
--- a/libs/androidfw/tests/ResTable_test.cpp
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -25,10 +25,10 @@
 
 #include "TestHelpers.h"
 #include "data/basic/R.h"
-#include "data/lib/R.h"
+#include "data/lib_one/R.h"
 
 namespace basic = com::android::basic;
-namespace lib = com::android::lib;
+namespace lib = com::android::lib_one;
 
 namespace android {
 
@@ -119,7 +119,7 @@
 
 TEST(ResTableTest, LibraryThemeIsAppliedCorrectly) {
   std::string contents;
-  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib/lib.apk",
+  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk",
                                       "resources.arsc", &contents));
 
   ResTable table;
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index c0011b6d..59cb18a 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -19,9 +19,13 @@
 #include "android-base/logging.h"
 
 #include "TestHelpers.h"
+#include "data/lib_one/R.h"
+#include "data/libclient/R.h"
 #include "data/styles/R.h"
 
 namespace app = com::android::app;
+namespace lib_one = com::android::lib_one;
+namespace libclient = com::android::libclient;
 
 namespace android {
 
@@ -30,10 +34,22 @@
   void SetUp() override {
     style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
     ASSERT_NE(nullptr, style_assets_);
+
+    libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk");
+    ASSERT_NE(nullptr, libclient_assets_);
+
+    lib_one_assets_ = ApkAssets::Load(GetTestDataPath() + "/lib_one/lib_one.apk");
+    ASSERT_NE(nullptr, lib_one_assets_);
+
+    lib_two_assets_ = ApkAssets::Load(GetTestDataPath() + "/lib_two/lib_two.apk");
+    ASSERT_NE(nullptr, lib_two_assets_);
   }
 
  protected:
   std::unique_ptr<ApkAssets> style_assets_;
+  std::unique_ptr<ApkAssets> libclient_assets_;
+  std::unique_ptr<ApkAssets> lib_one_assets_;
+  std::unique_ptr<ApkAssets> lib_two_assets_;
 };
 
 TEST_F(ThemeTest, EmptyTheme) {
@@ -174,6 +190,36 @@
   EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
 }
 
+TEST_F(ThemeTest, ResolveDynamicAttributesAndReferencesToSharedLibrary) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets(
+      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+  std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+  ASSERT_TRUE(theme->ApplyStyle(libclient::R::style::Theme, false /*force*/));
+
+  Res_value value;
+  uint32_t flags;
+  ApkAssetsCookie cookie;
+
+  // The attribute should be resolved to the final value.
+  cookie = theme->GetAttribute(libclient::R::attr::foo, &value, &flags);
+  ASSERT_NE(kInvalidCookie, cookie);
+  EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
+  EXPECT_EQ(700u, value.data);
+  EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
+
+  // The reference should be resolved to a TYPE_REFERENCE.
+  cookie = theme->GetAttribute(libclient::R::attr::bar, &value, &flags);
+  ASSERT_NE(kInvalidCookie, cookie);
+  EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
+
+  // lib_one is assigned package ID 0x03.
+  EXPECT_EQ(3u, util::get_package_id(value.data));
+  EXPECT_EQ(util::get_type_id(lib_one::R::string::foo), util::get_type_id(value.data));
+  EXPECT_EQ(util::get_entry_id(lib_one::R::string::foo), util::get_entry_id(value.data));
+}
+
 TEST_F(ThemeTest, CopyThemeSameAssetManager) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({style_assets_.get()});
diff --git a/libs/androidfw/tests/data/lib/lib.apk b/libs/androidfw/tests/data/lib/lib.apk
deleted file mode 100644
index 44c27c7..0000000
--- a/libs/androidfw/tests/data/lib/lib.apk
+++ /dev/null
Binary files differ
diff --git a/libs/androidfw/tests/data/lib/AndroidManifest.xml b/libs/androidfw/tests/data/lib_one/AndroidManifest.xml
similarity index 95%
rename from libs/androidfw/tests/data/lib/AndroidManifest.xml
rename to libs/androidfw/tests/data/lib_one/AndroidManifest.xml
index 02f5d3e..860adf7 100644
--- a/libs/androidfw/tests/data/lib/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/lib_one/AndroidManifest.xml
@@ -15,6 +15,6 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.lib">
+    package="com.android.lib_one">
     <application />
 </manifest>
diff --git a/libs/androidfw/tests/data/lib/R.h b/libs/androidfw/tests/data/lib_one/R.h
similarity index 80%
rename from libs/androidfw/tests/data/lib/R.h
rename to libs/androidfw/tests/data/lib_one/R.h
index bb22d22..fcaeb8d 100644
--- a/libs/androidfw/tests/data/lib/R.h
+++ b/libs/androidfw/tests/data/lib_one/R.h
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef TEST_DATA_LIB_R_H_
-#define TEST_DATA_LIB_R_H_
+#ifndef TEST_DATA_LIB_ONE_R_H_
+#define TEST_DATA_LIB_ONE_R_H_
 
 #include <cstdint>
 
 namespace com {
 namespace android {
-namespace lib {
+namespace lib_one {
 
 struct R {
   struct attr {
@@ -36,10 +36,16 @@
       Theme = 0x02020000,  // default
     };
   };
+
+  struct string {
+    enum : uint32_t {
+      foo = 0x02030000,  // default
+    };
+  };
 };
 
-}  // namespace lib
+}  // namespace lib_one
 }  // namespace android
 }  // namespace com
 
-#endif  // TEST_DATA_R_H_
+#endif  // TEST_DATA_LIB_ONE_R_H_
diff --git a/libs/androidfw/tests/data/lib/build b/libs/androidfw/tests/data/lib_one/build
similarity index 89%
rename from libs/androidfw/tests/data/lib/build
rename to libs/androidfw/tests/data/lib_one/build
index 5c3d02c..c6adf0b 100755
--- a/libs/androidfw/tests/data/lib/build
+++ b/libs/androidfw/tests/data/lib_one/build
@@ -17,4 +17,4 @@
 
 set -e
 
-aapt package -M AndroidManifest.xml -S res -F lib.apk -f --shared-lib
+aapt package -M AndroidManifest.xml -S res -F lib_one.apk -f --shared-lib
diff --git a/libs/androidfw/tests/data/lib_one/lib_one.apk b/libs/androidfw/tests/data/lib_one/lib_one.apk
new file mode 100644
index 0000000..f287554
--- /dev/null
+++ b/libs/androidfw/tests/data/lib_one/lib_one.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/lib/res/values/values.xml b/libs/androidfw/tests/data/lib_one/res/values/values.xml
similarity index 66%
copy from libs/androidfw/tests/data/lib/res/values/values.xml
copy to libs/androidfw/tests/data/lib_one/res/values/values.xml
index 51e3a40..752b7e9 100644
--- a/libs/androidfw/tests/data/lib/res/values/values.xml
+++ b/libs/androidfw/tests/data/lib_one/res/values/values.xml
@@ -15,11 +15,18 @@
 -->
 
 <resources>
+    <public type="attr" name="attr1" id="0x00010000" />
     <attr name="attr1" format="integer" />
+
+    <public type="attr" name="attr2" id="0x00010001" />
     <attr name="attr2" format="integer" />
 
+    <public type="style" name="Theme" id="0x00020000" />
     <style name="Theme">
-        <item name="com.android.lib:attr1">700</item>
-        <item name="com.android.lib:attr2">?com.android.lib:attr1</item>
+        <item name="com.android.lib_one:attr1">700</item>
+        <item name="com.android.lib_one:attr2">?com.android.lib_one:attr1</item>
     </style>
+
+    <public type="string" name="foo" id="0x00030000" />
+    <string name="foo">Foo from lib_one</string>
 </resources>
diff --git a/libs/androidfw/tests/data/lib/AndroidManifest.xml b/libs/androidfw/tests/data/lib_two/AndroidManifest.xml
similarity index 87%
copy from libs/androidfw/tests/data/lib/AndroidManifest.xml
copy to libs/androidfw/tests/data/lib_two/AndroidManifest.xml
index 02f5d3e..4b131e5 100644
--- a/libs/androidfw/tests/data/lib/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/lib_two/AndroidManifest.xml
@@ -14,7 +14,6 @@
      limitations under the License.
 -->
 
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.lib">
+<manifest package="com.android.lib_two">
     <application />
 </manifest>
diff --git a/libs/androidfw/tests/data/lib/R.h b/libs/androidfw/tests/data/lib_two/R.h
similarity index 72%
copy from libs/androidfw/tests/data/lib/R.h
copy to libs/androidfw/tests/data/lib_two/R.h
index bb22d22..c04a9d3 100644
--- a/libs/androidfw/tests/data/lib/R.h
+++ b/libs/androidfw/tests/data/lib_two/R.h
@@ -14,32 +14,26 @@
  * limitations under the License.
  */
 
-#ifndef TEST_DATA_LIB_R_H_
-#define TEST_DATA_LIB_R_H_
+#ifndef TEST_DATA_LIB_TWO_R_H_
+#define TEST_DATA_LIB_TWO_R_H_
 
 #include <cstdint>
 
 namespace com {
 namespace android {
-namespace lib {
+namespace lib_two {
 
 struct R {
-  struct attr {
+  struct string {
     enum : uint32_t {
-      attr1 = 0x02010000,  // default
-      attr2 = 0x02010001,  // default
-    };
-  };
-
-  struct style {
-    enum : uint32_t {
-      Theme = 0x02020000,  // default
+      LibraryString = 0x02020000,  // default
+      foo = 0x02020001, // default
     };
   };
 };
 
-}  // namespace lib
+}  // namespace lib_two
 }  // namespace android
 }  // namespace com
 
-#endif  // TEST_DATA_R_H_
+#endif  // TEST_DATA_LIB_TWO_R_H_
diff --git a/libs/androidfw/tests/data/lib/build b/libs/androidfw/tests/data/lib_two/build
similarity index 89%
copy from libs/androidfw/tests/data/lib/build
copy to libs/androidfw/tests/data/lib_two/build
index 5c3d02c..fd75e1d 100755
--- a/libs/androidfw/tests/data/lib/build
+++ b/libs/androidfw/tests/data/lib_two/build
@@ -17,4 +17,4 @@
 
 set -e
 
-aapt package -M AndroidManifest.xml -S res -F lib.apk -f --shared-lib
+aapt package -M AndroidManifest.xml -S res -F lib_two.apk -f --shared-lib
diff --git a/libs/androidfw/tests/data/lib_two/lib_two.apk b/libs/androidfw/tests/data/lib_two/lib_two.apk
new file mode 100644
index 0000000..ad44f9c
--- /dev/null
+++ b/libs/androidfw/tests/data/lib_two/lib_two.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/lib/res/values/values.xml b/libs/androidfw/tests/data/lib_two/res/values/values.xml
similarity index 73%
rename from libs/androidfw/tests/data/lib/res/values/values.xml
rename to libs/androidfw/tests/data/lib_two/res/values/values.xml
index 51e3a40..f4eea26 100644
--- a/libs/androidfw/tests/data/lib/res/values/values.xml
+++ b/libs/androidfw/tests/data/lib_two/res/values/values.xml
@@ -15,11 +15,9 @@
 -->
 
 <resources>
-    <attr name="attr1" format="integer" />
-    <attr name="attr2" format="integer" />
+    <public type="string" name="LibraryString" id="0x00020000" />
+    <string name="LibraryString">Hi from library two</string>
 
-    <style name="Theme">
-        <item name="com.android.lib:attr1">700</item>
-        <item name="com.android.lib:attr2">?com.android.lib:attr1</item>
-    </style>
+    <public type="string" name="foo" id="0x00020001" />
+    <string name="foo">Foo from lib_two</string>
 </resources>
diff --git a/libs/androidfw/tests/data/lib/AndroidManifest.xml b/libs/androidfw/tests/data/libclient/AndroidManifest.xml
similarity index 87%
copy from libs/androidfw/tests/data/lib/AndroidManifest.xml
copy to libs/androidfw/tests/data/libclient/AndroidManifest.xml
index 02f5d3e..8436383 100644
--- a/libs/androidfw/tests/data/lib/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/libclient/AndroidManifest.xml
@@ -14,7 +14,6 @@
      limitations under the License.
 -->
 
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.lib">
+<manifest package="com.android.libclient">
     <application />
 </manifest>
diff --git a/libs/androidfw/tests/data/lib/R.h b/libs/androidfw/tests/data/libclient/R.h
similarity index 75%
copy from libs/androidfw/tests/data/lib/R.h
copy to libs/androidfw/tests/data/libclient/R.h
index bb22d22..43d1f9b 100644
--- a/libs/androidfw/tests/data/lib/R.h
+++ b/libs/androidfw/tests/data/libclient/R.h
@@ -21,24 +21,31 @@
 
 namespace com {
 namespace android {
-namespace lib {
+namespace libclient {
 
 struct R {
   struct attr {
     enum : uint32_t {
-      attr1 = 0x02010000,  // default
-      attr2 = 0x02010001,  // default
+      foo = 0x7f010000,  // default
+      bar = 0x7f010001,  // default
     };
   };
 
   struct style {
     enum : uint32_t {
-      Theme = 0x02020000,  // default
+      Theme = 0x7f020000,  // default
+    };
+  };
+
+  struct string {
+    enum : uint32_t {
+      foo_one = 0x7f030000,  // default
+      foo_two = 0x7f030001,  // default
     };
   };
 };
 
-}  // namespace lib
+}  // namespace libclient
 }  // namespace android
 }  // namespace com
 
diff --git a/libs/androidfw/tests/data/lib/build b/libs/androidfw/tests/data/libclient/build
similarity index 66%
copy from libs/androidfw/tests/data/lib/build
copy to libs/androidfw/tests/data/libclient/build
index 5c3d02c..08310e3 100755
--- a/libs/androidfw/tests/data/lib/build
+++ b/libs/androidfw/tests/data/libclient/build
@@ -17,4 +17,14 @@
 
 set -e
 
-aapt package -M AndroidManifest.xml -S res -F lib.apk -f --shared-lib
+PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
+PATH_TO_LIB_ONE=../lib_one/lib_one.apk
+PATH_TO_LIB_TWO=../lib_two/lib_two.apk
+
+aapt package \
+    -M AndroidManifest.xml \
+    -S res \
+    -I $PATH_TO_FRAMEWORK_RES \
+    -I $PATH_TO_LIB_ONE \
+    -I $PATH_TO_LIB_TWO \
+    -F libclient.apk -f
diff --git a/libs/androidfw/tests/data/libclient/libclient.apk b/libs/androidfw/tests/data/libclient/libclient.apk
new file mode 100644
index 0000000..1799024
--- /dev/null
+++ b/libs/androidfw/tests/data/libclient/libclient.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/libclient/res/values/values.xml b/libs/androidfw/tests/data/libclient/res/values/values.xml
new file mode 100644
index 0000000..fead7c3
--- /dev/null
+++ b/libs/androidfw/tests/data/libclient/res/values/values.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<resources>
+    <public type="attr" name="foo" id="0x7f010000" />
+    <attr name="foo" />
+
+    <public type="attr" name="bar" id="0x7f010001" />
+    <attr name="bar" />
+
+    <public type="style" name="Theme" id="0x7f020000" />
+    <style name="Theme" parent="com.android.lib_one:style/Theme">
+      <item name="foo">?com.android.lib_one:attr/attr2</item>
+      <item name="bar">@com.android.lib_one:string/foo</item>
+    </style>
+
+    <public type="string" name="foo_one" id="0x7f030000" />
+    <string name="foo_one">@com.android.lib_one:string/foo</string>
+
+    <public type="string" name="foo_two" id="0x7f030001" />
+    <string name="foo_two">@com.android.lib_two:string/foo</string>
+</resources>
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8f7787b..138a5ef 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -7,7 +7,7 @@
 # Enables fine-grained GLES error checking
 # If set to true, every GLES call is wrapped & error checked
 # Has moderate overhead
-HWUI_ENABLE_OPENGL_VALIDATION := false
+HWUI_ENABLE_OPENGL_VALIDATION := true
 
 hwui_src_files := \
     hwui/Bitmap.cpp \
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 264944f..75ccffe 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -3134,6 +3134,15 @@
     public native final String getName();
 
     /**
+     *  Returns Analytics/Metrics data about the current content being
+     *
+     * @return a Bundle containint the set of attributes and values available
+     * for the media being handled by this instance of MediaCodec
+     *
+     */
+    public native Bundle getMetrics();
+
+    /**
      * Change a video encoder's target bitrate on the fly. The value is an
      * Integer object containing the new bitrate in bps.
      */
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 4023400..88dde53 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -1387,6 +1388,14 @@
     public native int getVideoHeight();
 
     /**
+     *  Returns Analytics/Metrics data about the current video in this player.
+     *
+     * @return the a map of attributes and values available for this video
+     * player or null if no metrics are available.
+     */
+    public native Bundle getMetrics();
+
+    /**
      * Checks whether the MediaPlayer is playing.
      *
      * @return true if currently playing, false otherwise
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 44f31526..9a08fbe 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1088,6 +1088,166 @@
         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
 
         /**
+         * The program type for movie.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_MOVIE = "TYPE_MOVIE";
+
+        /**
+         * The program type for TV series.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_TV_SERIES = "TYPE_TV_SERIES";
+
+        /**
+         * The program type for TV season.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_TV_SEASON = "TYPE_TV_SEASON";
+
+        /**
+         * The program type for TV episode.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_TV_EPISODE = "TYPE_TV_EPISODE";
+
+        /**
+         * The program type for clip.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_CLIP = "TYPE_CLIP";
+
+        /**
+         * The program type for event.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_EVENT = "TYPE_EVENT";
+
+        /**
+         * The program type for channel.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_CHANNEL = "TYPE_CHANNEL";
+
+        /**
+         * The program type for track.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_TRACK = "TYPE_TRACK";
+
+        /**
+         * The program type for album.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_ALBUM = "TYPE_ALBUM";
+
+        /**
+         * The program type for artist.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_ARTIST = "TYPE_ARTIST";
+
+        /**
+         * The program type for playlist.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_PLAYLIST = "TYPE_PLAYLIST";
+
+        /**
+         * The program type for station.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_STATION = "TYPE_STATION";
+
+        /**
+         * The watch next type for CONTINUE.
+         *
+         * @see #COLUMN_WATCH_NEXT_TYPE
+         */
+        public static final String WATCH_NEXT_TYPE_CONTINUE = "WATCH_NEXT_TYPE_CONTINUE";
+
+        /**
+         * The watch next type for NEXT.
+         *
+         * @see #COLUMN_WATCH_NEXT_TYPE
+         */
+        public static final String WATCH_NEXT_TYPE_NEXT = "WATCH_NEXT_TYPE_NEXT";
+
+        /**
+         * The watch next type for NEW.
+         *
+         * @see #COLUMN_WATCH_NEXT_TYPE
+         */
+        public static final String WATCH_NEXT_TYPE_NEW = "WATCH_NEXT_TYPE_NEW";
+
+        /**
+         * The aspect ratio for 16:9.
+         *
+         * @see #COLUMN_POSTER_ART_ASPECT_RATIO
+         * @see #COLUMN_THUMBNAIL_ASPECT_RATIO
+         */
+        public static final String ASPECT_RATIO_16_9 = "ASPECT_RATIO_16_9";
+
+        /**
+         * The aspect ratio for 3:2.
+         *
+         * @see #COLUMN_POSTER_ART_ASPECT_RATIO
+         * @see #COLUMN_THUMBNAIL_ASPECT_RATIO
+         */
+        public static final String ASPECT_RATIO_3_2 = "ASPECT_RATIO_3_2";
+
+        /**
+         * The aspect ratio for 1:1.
+         *
+         * @see #COLUMN_POSTER_ART_ASPECT_RATIO
+         * @see #COLUMN_THUMBNAIL_ASPECT_RATIO
+         */
+        public static final String ASPECT_RATIO_1_1 = "ASPECT_RATIO_1_1";
+
+        /**
+         * The aspect ratio for 2:3.
+         *
+         * @see #COLUMN_POSTER_ART_ASPECT_RATIO
+         * @see #COLUMN_THUMBNAIL_ASPECT_RATIO
+         */
+        public static final String ASPECT_RATIO_2_3 = "ASPECT_RATIO_2_3";
+
+        /**
+         * The availability for "available to this user".
+         *
+         * @see #COLUMN_AVAILABILITY
+         */
+        public static final String AVAILABILITY_AVAILABLE = "AVAILABILITY_AVAILABLE";
+
+        /**
+         * The availability for "free with subscription".
+         *
+         * @see #COLUMN_AVAILABILITY
+         */
+        public static final String AVAILABILITY_FREE_WITH_SUBSCRIPTION =
+                "AVAILABILITY_FREE_WITH_SUBSCRIPTION";
+
+        /**
+         * The availability for "paid content, either to-own or rental
+         * (user has not purchased/rented).
+         *
+         * @see #COLUMN_AVAILABILITY
+         */
+        public static final String AVAILABILITY_PAID_CONTENT = "AVAILABILITY_PAID_CONTENT";
+
+        /**
          * The interaction type for "listens".
          *
          * @see #COLUMN_INTERACTION_TYPE
@@ -1148,8 +1308,8 @@
          *
          * @see #COLUMN_REVIEW_RATING_STYLE
          */
-        public static final String REVIEW_RATING_STYLE_THUMPS_UP_DOWN =
-                "REVIEW_RATING_STYLE_THUMPS_UP_DOWN";
+        public static final String REVIEW_RATING_STYLE_THUMBS_UP_DOWN =
+                "REVIEW_RATING_STYLE_THUMBS_UP_DOWN";
 
         /**
          * The review rating style for 0 to 100 point system.
@@ -1171,6 +1331,44 @@
         public static final String COLUMN_CHANNEL_ID = "channel_id";
 
         /**
+         * The type of this program content.
+         *
+         * <p>The value should match one of the followings:
+         * {@link #TYPE_MOVIE},
+         * {@link #TYPE_TV_SERIES},
+         * {@link #TYPE_TV_SEASON},
+         * {@link #TYPE_TV_EPISODE},
+         * {@link #TYPE_CLIP},
+         * {@link #TYPE_EVENT},
+         * {@link #TYPE_CHANNEL},
+         * {@link #TYPE_TRACK},
+         * {@link #TYPE_ALBUM},
+         * {@link #TYPE_ARTIST},
+         * {@link #TYPE_PLAYLIST}, and
+         * {@link #TYPE_STATION}.
+         *
+         * <p>This is a required field if the program is from a {@link Channels#TYPE_PREVIEW}
+         * channel.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_TYPE = "type";
+
+        /**
+         * The "watch next" type of this program content.
+         *
+         * <p>The value should match one of the followings:
+         * {@link #WATCH_NEXT_TYPE_CONTINUE},
+         * {@link #WATCH_NEXT_TYPE_NEXT}, and
+         * {@link #WATCH_NEXT_TYPE_NEW}.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_WATCH_NEXT_TYPE = "watch_next_type";
+
+        /**
          * The title of this TV program.
          *
          * <p>If this program is an episodic TV show, it is recommended that the title is the series
@@ -1401,6 +1599,19 @@
         public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
 
         /**
+         * The aspect ratio of the poster art for this TV program.
+         *
+         * <p>The value should match one of the followings:
+         * {@link #ASPECT_RATIO_16_9},
+         * {@link #ASPECT_RATIO_3_2},
+         * {@link #ASPECT_RATIO_1_1}, and
+         * {@link #ASPECT_RATIO_2_3}.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_POSTER_ART_ASPECT_RATIO = "poster_art_aspect_ratio";
+
+        /**
          * The URI for the thumbnail of this TV program.
          *
          * <p>The system can generate a thumbnail from the poster art if this column is not
@@ -1423,6 +1634,104 @@
         public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
 
         /**
+         * The aspect ratio of the thumbnail for this TV program.
+         *
+         * <p>The value should match one of the followings:
+         * {@link #ASPECT_RATIO_16_9},
+         * {@link #ASPECT_RATIO_3_2},
+         * {@link #ASPECT_RATIO_1_1}, and
+         * {@link #ASPECT_RATIO_2_3}.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
+
+        /**
+         * The URI for the logo of this TV program.
+         *
+         * <p>This is a small badge shown on top of the poster art or thumbnail representing the
+         * source of the content.
+         *
+         * <p>The data in the column must be a URL, or a URI in one of the following formats:
+         *
+         * <ul>
+         * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+         * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+         * </li>
+         * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_LOGO = "logo";
+
+        /**
+         * The availability of this TV program.
+         *
+         * <p>The value should match one of the followings:
+         * {@link #AVAILABILITY_AVAILABLE},
+         * {@link #AVAILABILITY_FREE_WITH_SUBSCRIPTION}, and
+         * {@link #AVAILABILITY_PAID_CONTENT}.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_AVAILABILITY = "availability";
+
+        /**
+         * The starting price of this TV program.
+         *
+         * <p>This indicates the lowest regular acquisition cost of the content. It is only used
+         * if the availability of the program is {@link #AVAILABILITY_PAID_CONTENT}.
+         *
+         * <p>Type: TEXT
+         * @see #COLUMN_OFFER_PRICE
+         */
+        public static final String COLUMN_STARTING_PRICE = "starting_price";
+
+        /**
+         * The offer price of this TV program.
+         *
+         * <p>This is the promotional cost of the content. It is only used if the availability of
+         * the program is {@link #AVAILABILITY_PAID_CONTENT}.
+         *
+         * <p>Type: TEXT
+         * @see #COLUMN_STARTING_PRICE
+         */
+        public static final String COLUMN_OFFER_PRICE = "offer_price";
+
+        /**
+         * The release date of this TV program.
+         *
+         * <p>The value should be in the form of either "yyyy-MM-dd" or "yyyy".
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_RELEASE_DATE = "release_date";
+
+        /**
+         * The count of the items included in this TV program.
+         *
+         * <p>This is only relevant if the program represents a collection of items such as series,
+         * episodes, or music tracks.
+         *
+         * <p>Type: INTEGER
+         */
+        public static final String COLUMN_ITEM_COUNT = "item_count";
+
+        /**
+         * The flag indicating whether this TV program is live or not.
+         *
+         * <p>A value of 1 indicates that the content is airing and should be consumed now, a value
+         * of 0 indicates that the content is off the air and does not need to be consumed at the
+         * present time. If not specified, the value is set to 0 (not live) by default.
+         *
+         * <p>Type: INTEGER (boolean)
+         */
+        public static final String COLUMN_LIVE = "live";
+
+        /**
          * The flag indicating whether this TV program is searchable or not.
          *
          * <p>The columns of searchable programs can be read by other applications that have proper
@@ -1645,7 +1954,7 @@
          * The review rating score style used for {@link #COLUMN_REVIEW_RATING}.
          *
          * <p> The value should match one of the followings: {@link #REVIEW_RATING_STYLE_STARS},
-         * {@link #REVIEW_RATING_STYLE_THUMPS_UP_DOWN}, and {@link #REVIEW_RATING_STYLE_PERCENTAGE}.
+         * {@link #REVIEW_RATING_STYLE_THUMBS_UP_DOWN}, and {@link #REVIEW_RATING_STYLE_PERCENTAGE}.
          *
          * <p>Type: TEXT
          * @see #COLUMN_REVIEW_RATING
@@ -1657,7 +1966,7 @@
          *
          * <p>The format of the value is dependent on {@link #COLUMN_REVIEW_RATING_STYLE}. If the
          * style is {@link #REVIEW_RATING_STYLE_STARS}, the value should be a real number between
-         * 0.0 and 5.0. (e.g. "4.5") If the style is {@link #REVIEW_RATING_STYLE_THUMPS_UP_DOWN},
+         * 0.0 and 5.0. (e.g. "4.5") If the style is {@link #REVIEW_RATING_STYLE_THUMBS_UP_DOWN},
          * the value should be two integers, one for thumbs-up count and the other for thumbs-down
          * count, with a comma between them. (e.g. "200,40") If the style is
          * {@link #REVIEW_RATING_STYLE_PERCENTAGE}, the value shoule be a real number between 0 and
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 8d4271f..f69313c 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -11,6 +11,7 @@
     android_media_MediaDrm.cpp \
     android_media_MediaExtractor.cpp \
     android_media_MediaHTTPConnection.cpp \
+    android_media_MediaMetricsJNI.cpp \
     android_media_MediaMetadataRetriever.cpp \
     android_media_MediaMuxer.cpp \
     android_media_MediaPlayer.cpp \
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index c2c66fd..6f9883c 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -21,6 +21,7 @@
 #include "android_media_MediaCodec.h"
 
 #include "android_media_MediaCrypto.h"
+#include "android_media_MediaMetricsJNI.h"
 #include "android_media_Utils.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
@@ -618,6 +619,12 @@
     return OK;
 }
 
+status_t JMediaCodec::getMetrics(JNIEnv *, Parcel *reply) const {
+
+    status_t status = mCodec->getMetrics(reply);
+    return status;
+}
+
 status_t JMediaCodec::setParameters(const sp<AMessage> &msg) {
     return mCodec->setParameters(msg);
 }
@@ -1646,6 +1653,37 @@
     return NULL;
 }
 
+static jobject
+android_media_MediaCodec_getMetrics(JNIEnv *env, jobject thiz)
+{
+    ALOGV("android_media_MediaCodec_getMetrics");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+    if (codec == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    // get what we have for the metrics from the codec
+    Parcel reply;
+    status_t err = codec->getMetrics(env, &reply);
+    if (err != OK) {
+        ALOGE("getMetrics failed");
+        return (jobject) NULL;
+    }
+
+    // build and return the Bundle
+    MediaAnalyticsItem *item = new MediaAnalyticsItem;
+    item->readFromParcel(reply);
+    jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
+
+    // housekeeping
+    delete item;
+    item = NULL;
+
+    return mybundle;
+}
+
 static void android_media_MediaCodec_setParameters(
         JNIEnv *env, jobject thiz, jobjectArray keys, jobjectArray vals) {
     ALOGV("android_media_MediaCodec_setParameters");
@@ -1954,6 +1992,9 @@
     { "getName", "()Ljava/lang/String;",
       (void *)android_media_MediaCodec_getName },
 
+    { "getMetrics", "()Landroid/os/Bundle;",
+      (void *)android_media_MediaCodec_getMetrics},
+
     { "setParameters", "([Ljava/lang/String;[Ljava/lang/Object;)V",
       (void *)android_media_MediaCodec_setParameters },
 
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 5f0d3df..b3b1b3a 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -111,6 +111,8 @@
 
     status_t getName(JNIEnv *env, jstring *name) const;
 
+    status_t getMetrics(JNIEnv *env, Parcel *reply) const;
+
     status_t setParameters(const sp<AMessage> &params);
 
     void setVideoScalingMode(int mode);
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
new file mode 100644
index 0000000..fb606ba
--- /dev/null
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+#include <android_runtime/AndroidRuntime.h>
+#include <jni.h>
+#include <JNIHelp.h>
+
+#include "android_media_MediaMetricsJNI.h"
+#include <media/MediaAnalyticsItem.h>
+
+
+namespace android {
+
+// place the attributes into a java Bundle object
+// decide whether this is appropriately scoped here.
+// if we do it somewhere else, we have to figure a "give me all the attrs"
+// access to the inside of MediaAnalyticsItem
+jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle) {
+
+    jclass clazzBundle = env->FindClass("android/os/Bundle");
+    if (clazzBundle==NULL) {
+        ALOGD("can't find android/os/Bundle");
+        return NULL;
+    }
+    // sometimes the caller provides one for us to fill
+    if (mybundle == NULL) {
+        // create the bundle
+        jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V");
+        mybundle = env->NewObject(clazzBundle, constructID);
+        if (mybundle == NULL) {
+            return NULL;
+        }
+    }
+
+    // grab methods that we can invoke
+    jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V");
+    jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V");
+    jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V");
+    jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
+
+    // env, class, method, {parms}
+    //env->CallVoidMethod(env, mybundle, setIntID, jstr, jint);
+
+    // iterate through my attributes
+    // -- get name, get type, get value
+    // -- insert appropriately into the bundle
+    for (size_t i = 0 ; i < item->mPropCount; i++ ) {
+	    MediaAnalyticsItem::Prop *prop = &item->mProps[i];
+            // build the key parameter from prop->mName
+            jstring keyName = env->NewStringUTF(prop->mName);
+            // invoke the appropriate method to insert
+            switch (prop->mType) {
+                case MediaAnalyticsItem::kTypeInt32:
+                    env->CallVoidMethod(mybundle, setIntID,
+                                        keyName, (jint) prop->u.int32Value);
+                    break;
+                case MediaAnalyticsItem::kTypeInt64:
+                    env->CallVoidMethod(mybundle, setLongID,
+                                        keyName, (jlong) prop->u.int64Value);
+                    break;
+                case MediaAnalyticsItem::kTypeDouble:
+                    env->CallVoidMethod(mybundle, setDoubleID,
+                                        keyName, (jdouble) prop->u.doubleValue);
+                    break;
+                case MediaAnalyticsItem::kTypeCString:
+                    env->CallVoidMethod(mybundle, setStringID, keyName,
+                                        env->NewStringUTF(prop->u.CStringValue));
+                    break;
+                default:
+                        ALOGE("to_String bad item type: %d for %s",
+                              prop->mType, prop->mName);
+                        break;
+            }
+    }
+
+    return mybundle;
+}
+
+};  // namespace android
+
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h
new file mode 100644
index 0000000..d174212
--- /dev/null
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
+#define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
+
+#include <android_runtime/AndroidRuntime.h>
+#include <jni.h>
+#include <JNIHelp.h>
+#include <media/MediaAnalyticsItem.h>
+
+namespace android {
+
+class MediaMetricsJNI {
+public:
+    static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
+};
+
+};  // namespace android
+
+#endif //  _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 8225052..af59d81 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -23,6 +23,8 @@
 #include <media/AudioResamplerPublic.h>
 #include <media/IMediaHTTPService.h>
 #include <media/MediaPlayerInterface.h>
+#include <media/MediaAnalyticsItem.h>
+#include <media/stagefright/Utils.h>            // for FOURCC definition
 #include <stdio.h>
 #include <assert.h>
 #include <limits.h>
@@ -39,6 +41,7 @@
 #include "utils/String8.h"
 #include "android_media_BufferingParams.h"
 #include "android_media_MediaDataSource.h"
+#include "android_media_MediaMetricsJNI.h"
 #include "android_media_PlaybackParams.h"
 #include "android_media_SyncParams.h"
 #include "android_media_Utils.h"
@@ -684,6 +687,33 @@
     return (jint) h;
 }
 
+static jobject
+android_media_MediaPlayer_getMetrics(JNIEnv *env, jobject thiz)
+{
+    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+    if (mp == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    Parcel p;
+    int key = FOURCC('m','t','r','X');
+    status_t status = mp->getParameter(key, &p);
+    if (status != OK) {
+        ALOGD("getMetrics() failed: %d", status);
+        return (jobject) NULL;
+    }
+
+    MediaAnalyticsItem *item = new MediaAnalyticsItem;
+    item->readFromParcel(p);
+    jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
+
+    // housekeeping
+    delete item;
+    item = NULL;
+
+    return mybundle;
+}
 
 static jint
 android_media_MediaPlayer_getCurrentPosition(JNIEnv *env, jobject thiz)
@@ -1118,6 +1148,7 @@
     {"_stop",               "()V",                              (void *)android_media_MediaPlayer_stop},
     {"getVideoWidth",       "()I",                              (void *)android_media_MediaPlayer_getVideoWidth},
     {"getVideoHeight",      "()I",                              (void *)android_media_MediaPlayer_getVideoHeight},
+    {"getMetrics",          "()Landroid/os/Bundle;",            (void *)android_media_MediaPlayer_getMetrics},
     {"setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer_setPlaybackParams},
     {"getPlaybackParams", "()Landroid/media/PlaybackParams;", (void *)android_media_MediaPlayer_getPlaybackParams},
     {"setSyncParams",     "(Landroid/media/SyncParams;)V",  (void *)android_media_MediaPlayer_setSyncParams},
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index e0157a6..7bbca5e 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -103,10 +103,11 @@
 
     <!-- Bluetooth settings -->
 
-    <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_titles">
         <item>Use System Selection (Default)</item>
         <item>SBC</item>
+        <item>AAC</item>
         <item>aptX</item>
         <item>aptX HD</item>
         <item>LDAC</item>
@@ -119,18 +120,20 @@
         <item>1</item>
         <item>2</item>
         <item>3</item>
+        <item>4</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_summaries" >
         <item>Use System Selection (Default)</item>
         <item>SBC</item>
+        <item>AAC</item>
         <item>aptX</item>
         <item>aptX HD</item>
         <item>LDAC</item>
     </string-array>
 
-    <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
         <item>Use System Selection (Default)</item>
         <item>44.1 kHz</item>
@@ -148,7 +151,7 @@
         <item>8</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_sample_rate_summaries" >
         <item>Use System Selection (Default)</item>
         <item>44.1 kHz</item>
@@ -157,7 +160,7 @@
         <item>96.0 kHz</item>
     </string-array>
 
-    <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
         <item>Use System Selection (Default)</item>
         <item>16 bits/sample</item>
@@ -173,7 +176,7 @@
         <item>4</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries" >
         <item>Use System Selection (Default)</item>
         <item>16 bits/sample</item>
@@ -181,7 +184,7 @@
         <item>32 bits/sample</item>
     </string-array>
 
-    <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
         <item>Use System Selection (Default)</item>
         <item>Mono</item>
@@ -195,7 +198,7 @@
         <item>2</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_channel_mode_summaries" >
         <item>Use System Selection (Default)</item>
         <item>Mono</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
index b037a3da..fc697ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
@@ -198,7 +198,7 @@
             intent.putExtra(feedbackIntentExtraKey, packageNameKey);
             intent.putExtra(feedbackIntentNameKey, packageNameValue);
         }
-        intent.putExtra(EXTRA_THEME, 1 /* Light, dark action bar */);
+        intent.putExtra(EXTRA_THEME, 0 /* Light theme */);
         TypedArray array = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary});
         intent.putExtra(EXTRA_PRIMARY_COLOR, array.getColor(0, 0));
         array.recycle();
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/LinkAccessibilityHelper.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/LinkAccessibilityHelper.java
new file mode 100644
index 0000000..74b0c6b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/LinkAccessibilityHelper.java
@@ -0,0 +1,235 @@
+/*
+ * 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.settingslib.accessibility;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
+import android.text.Layout;
+import android.text.Spanned;
+import android.text.style.ClickableSpan;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * COPIED FROM SETUP WIZARD An accessibility delegate that allows {@link
+ * android.text.style.ClickableSpan} to be focused and clicked by accessibility services.
+ *
+ * <p>Sample usage:
+ *
+ * <pre>
+ * LinkAccessibilityHelper mAccessibilityHelper;
+ *
+ * private void init() {
+ *     mAccessibilityHelper = new LinkAccessibilityHelper(myTextView);
+ *     ViewCompat.setAccessibilityDelegate(myTextView, mLinkHelper);
+ * }
+ *
+ * {@literal @}Override
+ * protected boolean dispatchHoverEvent({@literal @}NonNull MotionEvent event) {
+ *     if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) {
+ *         return true;
+ *     }
+ *     return super.dispatchHoverEvent(event);
+ * }
+ * </pre>
+ *
+ * @see android.support.v4.widget.ExploreByTouchHelper
+ */
+public class LinkAccessibilityHelper extends ExploreByTouchHelper {
+
+    private static final String TAG = "LinkAccessibilityHelper";
+
+    private final TextView mView;
+    private final Rect mTempRect = new Rect();
+
+    public LinkAccessibilityHelper(TextView view) {
+        super(view);
+        mView = view;
+    }
+
+    @Override
+    protected int getVirtualViewAt(float x, float y) {
+        final CharSequence text = mView.getText();
+        if (text instanceof Spanned) {
+            final Spanned spannedText = (Spanned) text;
+            final int offset = getOffsetForPosition(mView, x, y);
+            ClickableSpan[] linkSpans = spannedText.getSpans(offset, offset, ClickableSpan.class);
+            if (linkSpans.length == 1) {
+                ClickableSpan linkSpan = linkSpans[0];
+                return spannedText.getSpanStart(linkSpan);
+            }
+        }
+        return INVALID_ID;
+    }
+
+    @Override
+    protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+        final CharSequence text = mView.getText();
+        if (text instanceof Spanned) {
+            final Spanned spannedText = (Spanned) text;
+            ClickableSpan[] linkSpans =
+                    spannedText.getSpans(0, spannedText.length(), ClickableSpan.class);
+            for (ClickableSpan span : linkSpans) {
+                virtualViewIds.add(spannedText.getSpanStart(span));
+            }
+        }
+    }
+
+    @Override
+    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+        final ClickableSpan span = getSpanForOffset(virtualViewId);
+        if (span != null) {
+            event.setContentDescription(getTextForSpan(span));
+        } else {
+            Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+            event.setContentDescription(mView.getText());
+        }
+    }
+
+    @Override
+    protected void onPopulateNodeForVirtualView(
+            int virtualViewId, AccessibilityNodeInfoCompat info) {
+        final ClickableSpan span = getSpanForOffset(virtualViewId);
+        if (span != null) {
+            info.setContentDescription(getTextForSpan(span));
+        } else {
+            Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+            info.setContentDescription(mView.getText());
+        }
+        info.setFocusable(true);
+        info.setClickable(true);
+        getBoundsForSpan(span, mTempRect);
+        if (mTempRect.isEmpty()) {
+            Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
+            mTempRect.set(0, 0, 1, 1);
+        }
+        info.setBoundsInParent(mTempRect);
+        info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+    }
+
+    @Override
+    protected boolean onPerformActionForVirtualView(
+            int virtualViewId, int action, Bundle arguments) {
+        if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+            ClickableSpan span = getSpanForOffset(virtualViewId);
+            if (span != null) {
+                span.onClick(mView);
+                return true;
+            } else {
+                Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+            }
+        }
+        return false;
+    }
+
+    private ClickableSpan getSpanForOffset(int offset) {
+        CharSequence text = mView.getText();
+        if (text instanceof Spanned) {
+            Spanned spannedText = (Spanned) text;
+            ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
+            if (spans.length == 1) {
+                return spans[0];
+            }
+        }
+        return null;
+    }
+
+    private CharSequence getTextForSpan(ClickableSpan span) {
+        CharSequence text = mView.getText();
+        if (text instanceof Spanned) {
+            Spanned spannedText = (Spanned) text;
+            return spannedText.subSequence(
+                    spannedText.getSpanStart(span), spannedText.getSpanEnd(span));
+        }
+        return text;
+    }
+
+    // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for the
+    // section on the first line.
+    private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
+        CharSequence text = mView.getText();
+        outRect.setEmpty();
+        if (text instanceof Spanned) {
+            final Layout layout = mView.getLayout();
+            if (layout != null) {
+                Spanned spannedText = (Spanned) text;
+                final int spanStart = spannedText.getSpanStart(span);
+                final int spanEnd = spannedText.getSpanEnd(span);
+                final float xStart = layout.getPrimaryHorizontal(spanStart);
+                final float xEnd = layout.getPrimaryHorizontal(spanEnd);
+                final int lineStart = layout.getLineForOffset(spanStart);
+                final int lineEnd = layout.getLineForOffset(spanEnd);
+                layout.getLineBounds(lineStart, outRect);
+                if (lineEnd == lineStart) {
+                    // If the span is on a single line, adjust both the left and right bounds
+                    // so outrect is exactly bounding the span.
+                    outRect.left = (int) Math.min(xStart, xEnd);
+                    outRect.right = (int) Math.max(xStart, xEnd);
+                } else {
+                    // If the span wraps across multiple lines, only use the first line (as returned
+                    // by layout.getLineBounds above), and adjust the "start" of outrect to where
+                    // the span starts, leaving the "end" of outrect at the end of the line.
+                    // ("start" being left for LTR, and right for RTL)
+                    if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
+                        outRect.right = (int) xStart;
+                    } else {
+                        outRect.left = (int) xStart;
+                    }
+                }
+
+                // Offset for padding
+                outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
+            }
+        }
+        return outRect;
+    }
+
+    // Compat implementation of TextView#getOffsetForPosition().
+
+    private static int getOffsetForPosition(TextView view, float x, float y) {
+        if (view.getLayout() == null) return -1;
+        final int line = getLineAtCoordinate(view, y);
+        return getOffsetAtCoordinate(view, line, x);
+    }
+
+    private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
+        x -= view.getTotalPaddingLeft();
+        // Clamp the position to inside of the view.
+        x = Math.max(0.0f, x);
+        x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
+        x += view.getScrollX();
+        return x;
+    }
+
+    private static int getLineAtCoordinate(TextView view, float y) {
+        y -= view.getTotalPaddingTop();
+        // Clamp the position to inside of the view.
+        y = Math.max(0.0f, y);
+        y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
+        y += view.getScrollY();
+        return view.getLayout().getLineForVertical((int) y);
+    }
+
+    private static int getOffsetAtCoordinate(TextView view, int line, float x) {
+        x = convertToLocalHorizontalCoordinate(view, x);
+        return view.getLayout().getOffsetForHorizontal(line, x);
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/LinkTextView.java b/packages/SettingsLib/src/com/android/settingslib/widget/LinkTextView.java
new file mode 100644
index 0000000..da86536
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/LinkTextView.java
@@ -0,0 +1,66 @@
+/*
+ * 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.settingslib.widget;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v4.view.ViewCompat;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.TextView;
+import com.android.settingslib.accessibility.LinkAccessibilityHelper;
+/**
+ * Copied from setup wizard. This TextView performs two functions. The first is to make it so the
+ * link behaves properly and becomes clickable. The second is that it makes the link visible to
+ * accessibility services.
+ */
+public class LinkTextView extends TextView {
+
+    private LinkAccessibilityHelper mAccessibilityHelper;
+
+    public LinkTextView(Context context) {
+        this(context, null);
+    }
+
+    public LinkTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mAccessibilityHelper = new LinkAccessibilityHelper(this);
+        ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
+    }
+
+    @Override
+    public void setText(CharSequence text, BufferType type) {
+        super.setText(text, type);
+        if (text instanceof Spanned) {
+            final ClickableSpan[] spans =
+                    ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
+            if (spans.length > 0) {
+                setMovementMethod(LinkMovementMethod.getInstance());
+            }
+        }
+    }
+
+    @Override
+    protected boolean dispatchHoverEvent(@NonNull MotionEvent event) {
+        if (mAccessibilityHelper.dispatchHoverEvent(event)) {
+            return true;
+        }
+        return super.dispatchHoverEvent(event);
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 799f388..c617994 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -16,9 +16,11 @@
 package com.android.settingslib.wifi;
 
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -38,6 +40,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.provider.Settings;
 import android.support.annotation.WorkerThread;
 import android.util.ArraySet;
 import android.util.Log;
@@ -136,6 +139,8 @@
     private final NetworkScoreManager mNetworkScoreManager;
     private final WifiNetworkScoreCache mScoreCache;
     private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
+    private boolean mNetworkScoringUiEnabled;
+    private final ContentObserver mObserver;
 
     @VisibleForTesting
     Scanner mScanner;
@@ -215,6 +220,16 @@
                 Message.obtain(mWorkHandler, WorkHandler.MSG_UPDATE_NETWORK_SCORES).sendToTarget();
             }
         });
+
+        mObserver = new ContentObserver(mWorkHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mNetworkScoringUiEnabled =
+                        Settings.Global.getInt(
+                                mContext.getContentResolver(),
+                                Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
+            }
+        };
     }
 
     /**
@@ -274,6 +289,11 @@
             }
         });
 
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED),
+                false /* notifyForDescendants */,
+                mObserver);
+        mObserver.onChange(false /* selfChange */); // Set the initial value for mScoringUiEnabled
 
         resumeScanning();
         if (!mRegistered) {
@@ -327,6 +347,7 @@
                 unregisterAndClearScoreCache();
             }
         });
+        mContext.getContentResolver().unregisterContentObserver(mObserver);
     }
 
     @WorkerThread
@@ -537,10 +558,11 @@
             }
         }
 
-
-        requestScoresForNetworkKeys(scoresToRequest);
-        for (AccessPoint ap : accessPoints) {
-            ap.updateScores(mScoreCache);
+        if (mNetworkScoringUiEnabled) {
+            requestScoresForNetworkKeys(scoresToRequest);
+            for (AccessPoint ap : accessPoints) {
+                ap.updateScores(mScoreCache);
+            }
         }
 
         // Pre-sort accessPoints to speed preference insertion
@@ -641,7 +663,7 @@
             if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
                 reorder = true;
             }
-            if (ap.updateScores(mScoreCache)) {
+            if (mNetworkScoringUiEnabled && ap.updateScores(mScoreCache)) {
                 reorder = true;
             }
         }
@@ -657,6 +679,10 @@
      * <p>Will trigger a resort and notify listeners of changes if applicable.
      */
     private void updateNetworkScores() {
+        if (!mNetworkScoringUiEnabled) {
+            return;
+        }
+
         // Lock required to prevent accidental copying of AccessPoint states while the modification
         // is in progress. see #copyAndNotifyListeners
         long before = System.currentTimeMillis();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index eaf0367..08736c7 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -110,7 +110,7 @@
     private HandlerThread mWorkerThread;
     private Looper mLooper;
     private Looper mMainLooper;
-    private int mOriginalSettingValue;
+    private int mOriginalScoringUiSettingValue;
 
     @Before
     public void setUp() {
@@ -175,19 +175,23 @@
                   }
                 }).when(mockWifiListener).onAccessPointsChanged();
 
-        mOriginalSettingValue = Settings.Global.getInt(
-            InstrumentationRegistry.getTargetContext().getContentResolver(),
-            Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
-            0 /* disabled */);
-
+        // Turn on Scoring UI features
+        mOriginalScoringUiSettingValue = Settings.Global.getInt(
+                InstrumentationRegistry.getTargetContext().getContentResolver(),
+                Settings.Global.NETWORK_SCORING_UI_ENABLED,
+                0 /* disabled */);
+        Settings.Global.putInt(
+                InstrumentationRegistry.getTargetContext().getContentResolver(),
+                Settings.Global.NETWORK_SCORING_UI_ENABLED,
+                1 /* enabled */);
     }
 
     @After
     public void cleanUp() {
         Settings.Global.putInt(
-            InstrumentationRegistry.getTargetContext().getContentResolver(),
-            Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
-            mOriginalSettingValue);
+                InstrumentationRegistry.getTargetContext().getContentResolver(),
+                Settings.Global.NETWORK_SCORING_UI_ENABLED,
+                mOriginalScoringUiSettingValue);
     }
 
     private static ScanResult buildScanResult1() {
@@ -333,9 +337,18 @@
 
         WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
 
+        CountDownLatch latch = new CountDownLatch(1);
+        doAnswer(
+                (invocation) -> {
+                        latch.countDown();
+                        return null;
+                }).when(mockNetworkScoreManager)
+                        .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
+
         // Test unregister
         tracker.stopTracking();
 
+        latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
         verify(mockNetworkScoreManager)
                 .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
     }
@@ -385,7 +398,28 @@
         assertTrue(aps.size() == 2);
         assertEquals(aps.get(0).getSsidStr(), SSID_2);
         assertEquals(aps.get(1).getSsidStr(), SSID_1);
+    }
 
+    @Test
+    public void scoreCacheUpdateScoresShouldNotChangeSortOrderWhenSortingDisabled()
+            throws InterruptedException {
+        Settings.Global.putInt(
+                InstrumentationRegistry.getTargetContext().getContentResolver(),
+                Settings.Global.NETWORK_SCORING_UI_ENABLED,
+                0 /* disabled */);
+
+        WifiTracker tracker = createTrackerAndInjectInitialScanResults();
+        List<AccessPoint> aps = tracker.getAccessPoints();
+        assertTrue(aps.size() == 2);
+        assertEquals(aps.get(0).getSsidStr(), SSID_1);
+        assertEquals(aps.get(1).getSsidStr(), SSID_2);
+
+        updateScoresAndWaitForAccessPointsChangedCallback();
+
+        aps = tracker.getAccessPoints();
+        assertTrue(aps.size() == 2);
+        assertEquals(aps.get(0).getSsidStr(), SSID_1);
+        assertEquals(aps.get(1).getSsidStr(), SSID_2);
     }
 
     @Test
@@ -405,6 +439,28 @@
     }
 
     @Test
+    public void noBadgesShouldBeInsertedIntoAccessPointWhenScoringUiDisabled()
+            throws InterruptedException {
+        Settings.Global.putInt(
+                InstrumentationRegistry.getTargetContext().getContentResolver(),
+                Settings.Global.NETWORK_SCORING_UI_ENABLED,
+                0 /* disabled */);
+
+        WifiTracker tracker = createTrackerAndInjectInitialScanResults();
+        updateScoresAndWaitForAccessPointsChangedCallback();
+
+        List<AccessPoint> aps = tracker.getAccessPoints();
+
+        for (AccessPoint ap : aps) {
+            if (ap.getSsidStr().equals(SSID_1)) {
+                assertEquals(ScoredNetwork.BADGING_NONE, ap.getBadge());
+            } else if (ap.getSsidStr().equals(SSID_2)) {
+                assertEquals(ScoredNetwork.BADGING_NONE, ap.getBadge());
+            }
+        }
+    }
+
+    @Test
     public void scoresShouldBeRequestedForNewScanResultOnly()  throws InterruptedException {
         mRequestScoresLatch = new CountDownLatch(2);
         WifiTracker tracker = createTrackerAndInjectInitialScanResults();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 25e1f16..7a9ba20 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -182,6 +182,18 @@
     private static final Bundle NULL_SETTING_BUNDLE = Bundle.forPair(
             Settings.NameValueTable.VALUE, null);
 
+    // Changes to these global settings are synchronously persisted
+    private static final Set<String> CRITICAL_GLOBAL_SETTINGS = new ArraySet<>();
+    static {
+        CRITICAL_GLOBAL_SETTINGS.add(Settings.Global.DEVICE_PROVISIONED);
+    }
+
+    // Changes to these secure settings are synchronously persisted
+    private static final Set<String> CRITICAL_SECURE_SETTINGS = new ArraySet<>();
+    static {
+        CRITICAL_SECURE_SETTINGS.add(Settings.Secure.USER_SETUP_COMPLETE);
+    }
+
     // Per user secure settings that moved to the for all users global settings.
     static final Set<String> sSecureMovedToGlobalSettings = new ArraySet<>();
     static {
@@ -949,18 +961,18 @@
                 case MUTATION_OPERATION_INSERT: {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
-                            getCallingPackage(), forceNotify);
+                            getCallingPackage(), forceNotify, CRITICAL_GLOBAL_SETTINGS);
                 }
 
                 case MUTATION_OPERATION_DELETE: {
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_GLOBAL,
-                            UserHandle.USER_SYSTEM, name, forceNotify);
+                            UserHandle.USER_SYSTEM, name, forceNotify, CRITICAL_GLOBAL_SETTINGS);
                 }
 
                 case MUTATION_OPERATION_UPDATE: {
                     return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
-                            getCallingPackage(), forceNotify);
+                            getCallingPackage(), forceNotify, CRITICAL_GLOBAL_SETTINGS);
                 }
 
                 case MUTATION_OPERATION_RESET: {
@@ -1156,18 +1168,18 @@
                 case MUTATION_OPERATION_INSERT: {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
                             owningUserId, name, value, tag, makeDefault,
-                            getCallingPackage(), forceNotify);
+                            getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
                 }
 
                 case MUTATION_OPERATION_DELETE: {
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SECURE,
-                            owningUserId, name, forceNotify);
+                            owningUserId, name, forceNotify, CRITICAL_SECURE_SETTINGS);
                 }
 
                 case MUTATION_OPERATION_UPDATE: {
                     return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SECURE,
                             owningUserId, name, value, tag, makeDefault,
-                            getCallingPackage(), forceNotify);
+                            getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
                 }
 
                 case MUTATION_OPERATION_RESET: {
@@ -1304,18 +1316,20 @@
                 case MUTATION_OPERATION_INSERT: {
                     validateSystemSettingValue(name, value);
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
-                            owningUserId, name, value, null, false, getCallingPackage(), false);
+                            owningUserId, name, value, null, false, getCallingPackage(),
+                            false, null);
                 }
 
                 case MUTATION_OPERATION_DELETE: {
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM,
-                            owningUserId, name, false);
+                            owningUserId, name, false, null);
                 }
 
                 case MUTATION_OPERATION_UPDATE: {
                     validateSystemSettingValue(name, value);
                     return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM,
-                            owningUserId, name, value, null, false, getCallingPackage(), false);
+                            owningUserId, name, value, null, false, getCallingPackage(),
+                            false, null);
                 }
             }
 
@@ -1689,7 +1703,7 @@
 
         return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
                 owningUserId, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders,
-                tag, makeDefault, getCallingPackage(), forceNotify);
+                tag, makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
     }
 
     private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
@@ -2234,7 +2248,8 @@
         }
 
         public boolean insertSettingLocked(int type, int userId, String name, String value,
-                String tag, boolean makeDefault, String packageName, boolean forceNotify) {
+                String tag, boolean makeDefault, String packageName, boolean forceNotify,
+                Set<String> criticalSettings) {
             final int key = makeKey(type, userId);
 
             boolean success = false;
@@ -2244,13 +2259,18 @@
                         tag, makeDefault, packageName);
             }
 
+            if (success && criticalSettings != null && criticalSettings.contains(name)) {
+                settingsState.persistSyncLocked();
+            }
+
             if (forceNotify || success) {
                 notifyForSettingsChange(key, name);
             }
             return success;
         }
 
-        public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify) {
+        public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify,
+                Set<String> criticalSettings) {
             final int key = makeKey(type, userId);
 
             boolean success = false;
@@ -2259,12 +2279,39 @@
                 success = settingsState.deleteSettingLocked(name);
             }
 
+            if (success && criticalSettings != null && criticalSettings.contains(name)) {
+                settingsState.persistSyncLocked();
+            }
+
             if (forceNotify || success) {
                 notifyForSettingsChange(key, name);
             }
             return success;
         }
 
+        public boolean updateSettingLocked(int type, int userId, String name, String value,
+                String tag, boolean makeDefault, String packageName, boolean forceNotify,
+                Set<String> criticalSettings) {
+            final int key = makeKey(type, userId);
+
+            boolean success = false;
+            SettingsState settingsState = peekSettingsStateLocked(key);
+            if (settingsState != null) {
+                success = settingsState.updateSettingLocked(name, value, tag,
+                        makeDefault, packageName);
+            }
+
+            if (success && criticalSettings != null && criticalSettings.contains(name)) {
+                settingsState.persistSyncLocked();
+            }
+
+            if (forceNotify || success) {
+                notifyForSettingsChange(key, name);
+            }
+
+            return success;
+        }
+
         public Setting getSettingLocked(int type, int userId, String name) {
             final int key = makeKey(type, userId);
 
@@ -2277,24 +2324,6 @@
             return settingsState.getSettingLocked(name);
         }
 
-        public boolean updateSettingLocked(int type, int userId, String name, String value,
-                String tag, boolean makeDefault, String packageName, boolean forceNotify) {
-            final int key = makeKey(type, userId);
-
-            boolean success = false;
-            SettingsState settingsState = peekSettingsStateLocked(key);
-            if (settingsState != null) {
-                success = settingsState.updateSettingLocked(name, value, tag,
-                        makeDefault, packageName);
-            }
-
-            if (forceNotify || success) {
-                notifyForSettingsChange(key, name);
-            }
-
-            return success;
-        }
-
         public void resetSettingsLocked(int type, int userId, String packageName, int mode,
                 String tag) {
             final int key = makeKey(type, userId);
@@ -2306,56 +2335,78 @@
             switch (mode) {
                 case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
                     for (String name : settingsState.getSettingNamesLocked()) {
+                        boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
                         if (packageName.equals(setting.getPackageName())) {
                             if (tag != null && !tag.equals(setting.getTag())) {
                                 continue;
                             }
-                            if (settingsState.resetSettingLocked(name, packageName)) {
+                            if (settingsState.resetSettingLocked(name)) {
+                                someSettingChanged = true;
                                 notifyForSettingsChange(key, name);
                             }
                         }
+                        if (someSettingChanged) {
+                            settingsState.persistSyncLocked();
+                        }
                     }
                 } break;
 
                 case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: {
                     for (String name : settingsState.getSettingNamesLocked()) {
+                        boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
                         if (!SettingsState.isSystemPackage(getContext(),
                                 setting.getPackageName())) {
-                            if (settingsState.resetSettingLocked(name, packageName)) {
+                            if (settingsState.resetSettingLocked(name)) {
+                                someSettingChanged = true;
                                 notifyForSettingsChange(key, name);
                             }
                         }
+                        if (someSettingChanged) {
+                            settingsState.persistSyncLocked();
+                        }
                     }
                 } break;
 
                 case Settings.RESET_MODE_UNTRUSTED_CHANGES: {
                     for (String name : settingsState.getSettingNamesLocked()) {
+                        boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
                         if (!SettingsState.isSystemPackage(getContext(),
                                 setting.getPackageName())) {
                             if (setting.isDefaultFromSystem()) {
-                                if (settingsState.resetSettingLocked(name, packageName)) {
+                                if (settingsState.resetSettingLocked(name)) {
+                                    someSettingChanged = true;
                                     notifyForSettingsChange(key, name);
                                 }
                             } else if (settingsState.deleteSettingLocked(name)) {
+                                someSettingChanged = true;
                                 notifyForSettingsChange(key, name);
                             }
                         }
+                        if (someSettingChanged) {
+                            settingsState.persistSyncLocked();
+                        }
                     }
                 } break;
 
                 case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
                     for (String name : settingsState.getSettingNamesLocked()) {
                         Setting setting = settingsState.getSettingLocked(name);
+                        boolean someSettingChanged = false;
                         if (setting.isDefaultFromSystem()) {
-                            if (settingsState.resetSettingLocked(name, packageName)) {
+                            if (settingsState.resetSettingLocked(name)) {
+                                someSettingChanged = true;
                                 notifyForSettingsChange(key, name);
                             }
                         } else if (settingsState.deleteSettingLocked(name)) {
+                            someSettingChanged = true;
                             notifyForSettingsChange(key, name);
                         }
+                        if (someSettingChanged) {
+                            settingsState.persistSyncLocked();
+                        }
                     }
                 } break;
             }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
index 2d59324..a6fadf9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
@@ -113,7 +113,7 @@
         String mKey = null;
         String mValue = null;
         String mPackageName = null;
-        String mToken = null;
+        String mTag = null;
         int mResetMode = -1;
         boolean mMakeDefault;
 
@@ -185,7 +185,7 @@
                         if (peekNextArg() == null) {
                             valid = true;
                         } else {
-                            mToken = getNextArg();
+                            mTag = getNextArg();
                             if (peekNextArg() == null) {
                                 valid = true;
                             } else {
@@ -218,10 +218,10 @@
                     // what we have so far is a valid command
                     valid = true;
                     // keep going; there may be another PUT arg
-                } else if (mToken == null) {
-                    mToken = arg;
-                    if ("default".equalsIgnoreCase(mToken)) {
-                        mToken = null;
+                } else if (mTag == null) {
+                    mTag = arg;
+                    if ("default".equalsIgnoreCase(mTag)) {
+                        mTag = null;
                         mMakeDefault = true;
                         if (peekNextArg() == null) {
                             valid = true;
@@ -282,7 +282,7 @@
                     pout.println(getForUser(iprovider, mUser, mTable, mKey));
                     break;
                 case PUT:
-                    putForUser(iprovider, mUser, mTable, mKey, mValue, mToken, mMakeDefault);
+                    putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault);
                     break;
                 case DELETE:
                     pout.println("Deleted "
@@ -294,7 +294,7 @@
                     }
                     break;
                 case RESET:
-                    resetForUser(iprovider, mUser, mTable, mToken);
+                    resetForUser(iprovider, mUser, mTable, mTag);
                     break;
                 default:
                     perr.println("Unspecified command");
@@ -358,7 +358,7 @@
         }
 
         void putForUser(IContentProvider provider, int userHandle, final String table,
-                final String key, final String value, String token, boolean makeDefault) {
+                final String key, final String value, String tag, boolean makeDefault) {
             final String callPutCommand;
             if ("system".equals(table)) {
                 callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM;
@@ -378,7 +378,9 @@
                 Bundle arg = new Bundle();
                 arg.putString(Settings.NameValueTable.VALUE, value);
                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
-                arg.putString(Settings.CALL_METHOD_TAG_KEY, token);
+                if (tag != null) {
+                    arg.putString(Settings.CALL_METHOD_TAG_KEY, tag);
+                }
                 if (makeDefault) {
                     arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
                 }
@@ -409,7 +411,7 @@
         }
 
         void resetForUser(IContentProvider provider, int userHandle,
-                String table, String token) {
+                String table, String tag) {
             final String callResetCommand;
             if ("secure".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_SECURE;
             else if ("global".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_GLOBAL;
@@ -422,7 +424,9 @@
                 Bundle arg = new Bundle();
                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
                 arg.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, mResetMode);
-                arg.putString(Settings.CALL_METHOD_TAG_KEY, token);
+                if (tag != null) {
+                    arg.putString(Settings.CALL_METHOD_TAG_KEY, tag);
+                }
                 String packageName = mPackageName != null ? mPackageName : resolveCallingPackage();
                 arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
                 provider.call(packageName, callResetCommand, null, arg);
@@ -465,9 +469,9 @@
                 pw.println("      Print this help text.");
                 pw.println("  get [--user <USER_ID> | current] NAMESPACE KEY");
                 pw.println("      Retrieve the current value of KEY.");
-                pw.println("  put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TOKEN] [default]");
+                pw.println("  put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TAG] [default]");
                 pw.println("      Change the contents of KEY to VALUE.");
-                pw.println("      TOKEN to associate with the setting.");
+                pw.println("      TAG to associate with the setting.");
                 pw.println("      {default} to set as the default, case-insensitive only for global/secure namespace");
                 pw.println("  delete NAMESPACE KEY");
                 pw.println("      Delete the entry for KEY.");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index a74be35..56ae618 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -64,6 +64,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * This class contains the state for one type of settings. It is responsible
@@ -129,7 +130,7 @@
     private static final String HISTORICAL_OPERATION_INITIALIZE = "initialize";
     private static final String HISTORICAL_OPERATION_RESET = "reset";
 
-    private static final String SHELL_PACKAGE_NAME = "shell";
+    private static final String SHELL_PACKAGE_NAME = "com.android.shell";
     private static final String ROOT_PACKAGE_NAME = "root";
 
     private static final String NULL_VALUE = "null";
@@ -307,7 +308,7 @@
         Setting newState;
 
         if (oldState != null) {
-            if (!oldState.update(value, makeDefault, packageName, tag)) {
+            if (!oldState.update(value, makeDefault, packageName, tag, false)) {
                 return false;
             }
             newState = oldState;
@@ -351,7 +352,7 @@
     }
 
     // The settings provider must hold its lock when calling here.
-    public boolean resetSettingLocked(String name, String packageName) {
+    public boolean resetSettingLocked(String name) {
         if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) {
             return false;
         }
@@ -362,7 +363,7 @@
         String oldValue = setting.getValue();
         String oldDefaultValue = setting.getDefaultValue();
 
-        if (!setting.reset(packageName)) {
+        if (!setting.reset()) {
             return false;
         }
 
@@ -817,7 +818,7 @@
         public Setting(String name, String value, boolean makeDefault, String packageName,
                 String tag) {
             this.name = name;
-            update(value, makeDefault, packageName, tag);
+            update(value, makeDefault, packageName, tag, false);
         }
 
         public Setting(String name, String value, String defaultValue,
@@ -877,16 +878,18 @@
         }
 
         /** @return whether the value changed */
-        public boolean reset(String packageName) {
-            return update(this.defaultValue, false, packageName, null);
+        public boolean reset() {
+            return update(this.defaultValue, false, packageName, null, true);
         }
 
-        public boolean update(String value, boolean setDefault, String packageName, String tag) {
+        public boolean update(String value, boolean setDefault, String packageName, String tag,
+                boolean forceNonSystemPackage) {
             if (NULL_VALUE.equals(value)) {
                 value = null;
             }
 
-            final boolean callerSystem = !isNull() && isSystemPackage(mContext, packageName);
+            final boolean callerSystem = !forceNonSystemPackage &&
+                    !isNull() && isSystemPackage(mContext, packageName);
             // Settings set by the system are always defaults.
             if (callerSystem) {
                 setDefault = true;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
index 0454b51..ab23af3 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
@@ -206,19 +206,22 @@
         resetToDefaultsViaShell(type, packageName, null);
     }
 
-    protected static void resetToDefaultsViaShell(int type, String packageName, String token)
+    protected static void resetToDefaultsViaShell(int type, String packageName, String tag)
             throws IOException {
         switch (type) {
             case SETTING_TYPE_GLOBAL: {
-                executeShellCommand("settings reset global " + packageName + " " + token);
+                executeShellCommand("settings reset global " + packageName + " "
+                        + (tag != null ? tag : ""));
             } break;
 
             case SETTING_TYPE_SECURE: {
-                executeShellCommand("settings reset secure " + packageName + " " + token);
+                executeShellCommand("settings reset secure " + packageName + " "
+                        + (tag != null ? tag : ""));
             } break;
 
             case SETTING_TYPE_SYSTEM: {
-                executeShellCommand("settings reset system " + packageName + " " + token);
+                executeShellCommand("settings reset system " + packageName + " "
+                        + (tag != null ? tag : ""));
             } break;
 
             default: {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
index b31b199..e75ecb7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
@@ -64,7 +64,7 @@
  *        new PluginListener<OverlayPlugin>() {
  *        @Override
  *        public void onPluginConnected(OverlayPlugin plugin) {
- *            PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
+ *            StatusBar phoneStatusBar = getComponent(StatusBar.class);
  *            if (phoneStatusBar != null) {
  *                plugin.setup(phoneStatusBar.getStatusBarWindow(),
  *                phoneStatusBar.getNavigationBarView());
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
index c0a48a8..93ba39c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
@@ -3,6 +3,7 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.view.View;
 
@@ -48,7 +49,7 @@
     }
 
     public interface SnoozeListener {
-        public void snoozeNotification(StatusBarNotification sbn, long snoozeUntil);
+        public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption);
     }
 
     public static class MenuItem {
@@ -71,4 +72,19 @@
             return false;
         }
     }
+
+    public static class SnoozeOption {
+        public SnoozeCriterion criterion;
+        public int snoozeForMinutes;
+        public CharSequence description;
+        public CharSequence confirmation;
+
+        public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc,
+                CharSequence confirm) {
+            criterion = crit;
+            snoozeForMinutes = minsToSnoozeFor;
+            description = desc;
+            confirmation = confirm;
+        }
+    }
 }
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 364885a..6d76798 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -11,7 +11,7 @@
 }
 
 -keep class com.android.systemui.statusbar.car.CarStatusBar
--keep class com.android.systemui.statusbar.phone.PhoneStatusBar
+-keep class com.android.systemui.statusbar.phone.StatusBar
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
 -keep class com.android.systemui.car.CarSystemUIFactory
 -keep class com.android.systemui.SystemUIFactory
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 789b765..5ee242d 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -23,6 +23,7 @@
     android:layout_gravity="top|center_horizontal">
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/icon"
+        android:contentDescription="@string/recents_app_info_button_label"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|start"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b7647cf..2f39d1d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -43,7 +43,7 @@
 
     <!-- Component to be used as the status bar service.  Must implement the IStatusBar
      interface.  This name is in the ComponentName flattened format (package/class)  -->
-    <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.PhoneStatusBar</string>
+    <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
 
     <!-- Whether or not we show the number in the bar. -->
     <bool name="config_statusBarShowNumber">false</bool>
@@ -301,7 +301,5 @@
     <item type="id" name="action_split_task_to_left" />
     <item type="id" name="action_split_task_to_right" />
     <item type="id" name="action_split_task_to_top" />
-    <item type="id" name="action_open" />
-    <item type="id" name="action_dimiss" />
 
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7f4baa5..d3e965a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -794,10 +794,6 @@
     <string name="recents_multistack_add_stack_dialog_split_vertical">Split Vertical</string>
     <!-- Recents: MultiStack add stack split custom radio button. [CHAR LIMIT=NONE] -->
     <string name="recents_multistack_add_stack_dialog_split_custom">Split Custom</string>
-    <!-- Recents: Accessibility dismiss label -->
-    <string name="recents_accessibility_dismissed">Dismiss</string>
-    <!-- Recents: Accessibility open label -->
-    <string name="recents_accessibility_open">Open</string>
     <!-- Recents: Accessibility split to the top -->
     <string name="recents_accessibility_split_screen_top">Split screen to the top</string>
     <!-- Recents: Accessibility split to the left -->
@@ -1390,6 +1386,8 @@
     <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 options: don't snooze option. [CHAR LIMIT=50] -->
+    <string name="snooze_option_dont_snooze">Don\'t snooze</string>
     <!-- Notification: Menu row: Snooze undo button label. [CHAR LIMIT=50]-->
     <string name="snooze_undo">UNDO</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
index 4ae81a7..14c67fe 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -19,7 +19,7 @@
 
 /**
  * Single common instance of ActivityStarter that can be gotten and referenced from anywhere, but
- * delegates to an actual implementation such as PhoneStatusBar, assuming it exists.
+ * delegates to an actual implementation such as StatusBar, assuming it exists.
  */
 public class ActivityStarterDelegate implements ActivityStarter {
 
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index c14b17f..1d55ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -27,7 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.LatencyTracker;
 import com.android.systemui.statusbar.phone.FingerprintUnlockController;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 /**
  * Class that only runs on debuggable builds that listens to broadcasts that simulate actions in the
@@ -72,7 +72,7 @@
     }
 
     private void fakeWakeAndUnlock() {
-        FingerprintUnlockController fingerprintUnlockController = getComponent(PhoneStatusBar.class)
+        FingerprintUnlockController fingerprintUnlockController = getComponent(StatusBar.class)
                 .getFingerprintUnlockController();
         fingerprintUnlockController.onFingerprintAcquired();
         fingerprintUnlockController.onFingerprintAuthenticated(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SystemBars.java b/packages/SystemUI/src/com/android/systemui/SystemBars.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/SystemBars.java
rename to packages/SystemUI/src/com/android/systemui/SystemBars.java
index 275fd70..6623cabe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SystemBars.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemBars.java
@@ -1,20 +1,18 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui;
 
 import android.content.res.Configuration;
 import android.provider.Settings;
@@ -36,7 +34,7 @@
     private static final int WAIT_FOR_BARS_TO_DIE = 500;
 
     // in-process fallback implementation, per the product config
-    private BaseStatusBar mStatusBar;
+    private SystemUI mStatusBar;
 
     @Override
     public void start() {
@@ -71,7 +69,7 @@
             throw andLog("Error loading status bar component: " + clsName, t);
         }
         try {
-            mStatusBar = (BaseStatusBar) cls.newInstance();
+            mStatusBar = (SystemUI) cls.newInstance();
         } catch (Throwable t) {
             throw andLog("Error creating status bar component: " + clsName, t);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index afe88c1..9515585 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,8 +42,7 @@
 import com.android.systemui.shortcut.ShortcutKeyDispatcher;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.SystemBars;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.usb.StorageNotification;
 import com.android.systemui.util.NotificationChannels;
@@ -210,10 +209,10 @@
                 new PluginListener<OverlayPlugin>() {
             @Override
             public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
-                PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
-                if (phoneStatusBar != null) {
-                    plugin.setup(phoneStatusBar.getStatusBarWindow(),
-                            phoneStatusBar.getNavigationBarView());
+                StatusBar statusBar = getComponent(StatusBar.class);
+                if (statusBar != null) {
+                    plugin.setup(statusBar.getStatusBarWindow(),
+                            statusBar.getNavigationBarView());
                 }
             }
         }, OverlayPlugin.VERSION, true /* Allow multiple plugins */);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index ec11812..1ff0701 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -26,10 +26,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Dependency.DependencyProvider;
-import com.android.systemui.R;
-import com.android.systemui.assist.AssistManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
@@ -37,26 +34,11 @@
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarWindowManager;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.volume.VolumeDialogController;
 
 /**
@@ -113,8 +95,8 @@
     }
 
     public NotificationIconAreaController createNotificationIconAreaController(Context context,
-            PhoneStatusBar phoneStatusBar) {
-        return new NotificationIconAreaController(context, phoneStatusBar);
+            StatusBar statusBar) {
+        return new NotificationIconAreaController(context, statusBar);
     }
 
     public KeyguardIndicationController createKeyguardIndicationController(Context context,
@@ -122,7 +104,7 @@
         return new KeyguardIndicationController(context, indicationArea, lockIcon);
     }
 
-    public QSTileHost createQSTileHost(Context context, PhoneStatusBar statusBar,
+    public QSTileHost createQSTileHost(Context context, StatusBar statusBar,
             StatusBarIconController iconController) {
         return new QSTileHost(context, statusBar, iconController);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ce89aab..99c8c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -80,7 +80,7 @@
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.statusbar.phone.FingerprintUnlockController;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
@@ -1955,11 +1955,11 @@
         Trace.endSection();
     }
 
-    public StatusBarKeyguardViewManager registerStatusBar(PhoneStatusBar phoneStatusBar,
+    public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
             ViewGroup container, StatusBarWindowManager statusBarWindowManager,
             ScrimController scrimController,
             FingerprintUnlockController fingerprintUnlockController) {
-        mStatusBarKeyguardViewManager.registerStatusBar(phoneStatusBar, container,
+        mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
                 statusBarWindowManager, scrimController, fingerprintUnlockController,
                 mDismissCallbackRegistry);
         return mStatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 94ea4dc..82ec69d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -41,7 +41,7 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.NotificationChannels;
 
@@ -98,7 +98,7 @@
     private SystemUIDialog mHighTempDialog;
 
     public PowerNotificationWarnings(Context context, NotificationManager notificationManager,
-            PhoneStatusBar phoneStatusBar) {
+            StatusBar statusBar) {
         mContext = context;
         mNoMan = notificationManager;
         mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 1d4a5c7..3d36868 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -36,7 +36,7 @@
 import android.util.Slog;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -72,7 +72,7 @@
         mWarnings = new PowerNotificationWarnings(
                 mContext,
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE),
-                getComponent(PhoneStatusBar.class));
+                getComponent(StatusBar.class));
 
         ContentObserver obs = new ContentObserver(mHandler) {
             @Override
@@ -250,8 +250,8 @@
     }
 
     private void updateTemperatureWarning() {
-        PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
-        if (phoneStatusBar != null && phoneStatusBar.isDeviceInVrMode()) {
+        StatusBar statusBar = getComponent(StatusBar.class);
+        if (statusBar != null && statusBar.isDeviceInVrMode()) {
             // ensure the warning isn't showing, since VR shows its own warning
             mWarnings.dismissTemperatureWarning();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 5c23eb7..ce72942 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -307,8 +307,10 @@
     }
 
     public void destroy() {
-        mServices.values().forEach(service -> service.handleDestroy());
-        mContext.unregisterReceiver(mRequestListeningReceiver);
+        synchronized (mServices) {
+            mServices.values().forEach(service -> service.handleDestroy());
+            mContext.unregisterReceiver(mRequestListeningReceiver);
+        }
     }
 
     private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index 7e04b67..06f4d9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.drawable.Drawable;
 import android.service.quicksettings.Tile;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -42,8 +41,6 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSTile;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-import com.android.systemui.qs.external.TileColorPicker;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import java.text.NumberFormat;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 06fadd1..a6fe0ea 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -88,7 +88,7 @@
 import com.android.systemui.recents.model.TaskStack;
 import com.android.systemui.recents.views.RecentsView;
 import com.android.systemui.recents.views.SystemBarScrimViews;
-import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -283,7 +283,7 @@
         dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
                 overrideAnimation));
         Recents.getSystemServices().sendCloseSystemWindows(
-                BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+                StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
         EventBus.getDefault().send(dismissEvent);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index cf6357b..9a8b267 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -18,7 +18,6 @@
 
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
 import static android.app.ActivityManager.StackId.isHomeOrRecentsStack;
 import static android.view.View.MeasureSpec;
 
@@ -77,9 +76,8 @@
 import com.android.systemui.recents.views.TaskViewHeader;
 import com.android.systemui.recents.views.TaskViewTransform;
 import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
 
@@ -229,7 +227,7 @@
      */
     public void onStartScreenPinning(Context context, int taskId) {
         SystemUIApplication app = (SystemUIApplication) context;
-        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+        StatusBar statusBar = app.getComponent(StatusBar.class);
         if (statusBar != null) {
             statusBar.showScreenPinningRequest(taskId, false);
         }
@@ -351,7 +349,7 @@
                         growTarget);
 
                 // Only close the other system windows if we are actually showing recents
-                ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
+                ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
                 mLastToggleTime = SystemClock.elapsedRealtime();
             }
         } catch (ActivityNotFoundException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index a2a8199..a691a424 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -30,6 +30,8 @@
 import android.widget.FrameLayout.LayoutParams;
 
 import com.android.systemui.R;
+import com.android.systemui.pip.tv.PipManager;
+import com.android.systemui.pip.tv.PipRecentsOverlayManager;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
@@ -57,9 +59,7 @@
 import com.android.systemui.recents.tv.views.TaskCardView;
 import com.android.systemui.recents.tv.views.TaskStackHorizontalGridView;
 import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.pip.tv.PipManager;
-import com.android.systemui.pip.tv.PipRecentsOverlayManager;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -258,7 +258,7 @@
             @Override
             public void run() {
                 Recents.getSystemServices().sendCloseSystemWindows(
-                        BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+                        StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
             }
         };
         DismissRecentsToHomeAnimationStarted dismissEvent =
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 2bd651b..6a66fca7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -51,7 +51,7 @@
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -170,7 +170,7 @@
             }
         }
         Recents.getSystemServices().sendCloseSystemWindows(
-                BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+                StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
     }
 
     public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index e941c3b..45e766c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -258,11 +258,6 @@
     }
 
     @Override
-    public void addChildrenForAccessibility(ArrayList<View> outChildren) {
-        // Prevent any children from being focusable during talkback
-    }
-
-    @Override
     public boolean hasOverlappingRendering() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
index 759daf1..2c3e78f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Point;
 import android.os.Bundle;
 import android.util.SparseArray;
@@ -29,6 +28,7 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.Utilities;
@@ -39,8 +39,6 @@
 
     private final TaskView mTaskView;
 
-    protected static final int OPEN = R.id.action_open;
-    protected static final int DIMISS = R.id.action_dimiss;
     protected static final int SPLIT_TASK_TOP = R.id.action_split_task_to_top;
     protected static final int SPLIT_TASK_LEFT = R.id.action_split_task_to_left;
     protected static final int SPLIT_TASK_RIGHT = R.id.action_split_task_to_right;
@@ -50,10 +48,6 @@
     public TaskViewAccessibilityDelegate(TaskView taskView) {
         mTaskView = taskView;
         Context context = taskView.getContext();
-        mActions.put(OPEN, new AccessibilityAction(OPEN,
-                context.getString(R.string.recents_accessibility_open)));
-        mActions.put(DIMISS, new AccessibilityAction(DIMISS,
-                context.getString(R.string.recents_accessibility_dismissed)));
         mActions.put(SPLIT_TASK_TOP, new AccessibilityAction(SPLIT_TASK_TOP,
                 context.getString(R.string.recents_accessibility_split_screen_top)));
         mActions.put(SPLIT_TASK_LEFT, new AccessibilityAction(SPLIT_TASK_LEFT,
@@ -65,8 +59,6 @@
     @Override
     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(host, info);
-        info.addAction(mActions.get(OPEN));
-        info.addAction(mActions.get(DIMISS));
         if (ActivityManager.supportsSplitScreenMultiWindow()
                 && !Recents.getSystemServices().hasDockedTask()) {
             TaskStack.DockState[] dockStates = Recents.getConfiguration()
@@ -85,11 +77,7 @@
 
     @Override
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
-        if (action == OPEN) {
-            mTaskView.onClick(host);
-        } else if (action == DIMISS) {
-            mTaskView.dismissTask();
-        } else if (action == SPLIT_TASK_TOP) {
+        if (action == SPLIT_TASK_TOP) {
             simulateDragIntoMultiwindow(TaskStack.DockState.TOP);
         } else if (action == SPLIT_TASK_LEFT) {
             simulateDragIntoMultiwindow(TaskStack.DockState.LEFT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index b318ea7..0777163 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -462,6 +462,7 @@
                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
                 mLightDismissDrawable : mDarkDismissDrawable);
+        mDismissButton.setContentDescription(t.dismissDescription);
         mDismissButton.setOnClickListener(this);
         mDismissButton.setClickable(false);
         ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
@@ -498,6 +499,7 @@
 
         // In accessibility, a single click on the focused app info button will show it
         if (touchExplorationEnabled) {
+            mIconView.setContentDescription(t.appInfoDescription);
             mIconView.setOnClickListener(this);
             mIconView.setClickable(true);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 3cd2a7a..b9a0f74 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -44,6 +44,7 @@
     private boolean mVisible = false;
     private boolean mMinimized = false;
     private boolean mAdjustedForIme = false;
+    private boolean mHomeStackResizable = false;
     private ForcedResizableInfoActivityController mForcedResizableController;
 
     @Override
@@ -75,6 +76,7 @@
         mView = (DividerView)
                 LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
         mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
+        mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
         final int size = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_divider_thickness);
         final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
@@ -92,7 +94,7 @@
         removeDivider();
         addDivider(configuration);
         if (mMinimized) {
-            mView.setMinimizedDockStack(true);
+            mView.setMinimizedDockStack(true, mHomeStackResizable);
             updateTouchable();
         }
     }
@@ -106,13 +108,14 @@
                     mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
 
                     // Update state because animations won't finish.
-                    mView.setMinimizedDockStack(mMinimized);
+                    mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
                 }
             }
         });
     }
 
-    private void updateMinimizedDockedStack(final boolean minimized, final long animDuration) {
+    private void updateMinimizedDockedStack(final boolean minimized, final long animDuration,
+            final boolean isHomeStackResizable) {
         mView.post(new Runnable() {
             @Override
             public void run() {
@@ -120,9 +123,9 @@
                     mMinimized = minimized;
                     updateTouchable();
                     if (animDuration > 0) {
-                        mView.setMinimizedDockStack(minimized, animDuration);
+                        mView.setMinimizedDockStack(minimized, animDuration, isHomeStackResizable);
                     } else {
-                        mView.setMinimizedDockStack(minimized);
+                        mView.setMinimizedDockStack(minimized, isHomeStackResizable);
                     }
                 }
             }
@@ -139,7 +142,7 @@
     }
 
     private void updateTouchable() {
-        mWindowManager.setTouchable(!mMinimized && !mAdjustedForIme);
+        mWindowManager.setTouchable((mHomeStackResizable || !mMinimized) && !mAdjustedForIme);
     }
 
     @Override
@@ -162,9 +165,10 @@
         }
 
         @Override
-        public void onDockedStackMinimizedChanged(boolean minimized, long animDuration)
-                throws RemoteException {
-            updateMinimizedDockedStack(minimized, animDuration);
+        public void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
+                boolean isHomeStackResizable) throws RemoteException {
+            mHomeStackResizable = isHomeStackResizable;
+            updateMinimizedDockedStack(minimized, animDuration, isHomeStackResizable);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 47d2def..49035ba 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -64,6 +64,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
+import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.activity.UndockingTaskEvent;
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
@@ -123,6 +124,8 @@
     private boolean mMoving;
     private int mTouchSlop;
     private boolean mBackgroundLifted;
+    private boolean mIsInMinimizeInteraction;
+    private int mDividerPositionBeforeMinimized;
 
     private int mDividerInsets;
     private int mDisplayWidth;
@@ -145,6 +148,7 @@
     private VelocityTracker mVelocityTracker;
     private FlingAnimationUtils mFlingAnimationUtils;
     private DividerSnapAlgorithm mSnapAlgorithm;
+    private DividerSnapAlgorithm mMinimizedSnapAlgorithm;
     private final Rect mStableInsets = new Rect();
 
     private boolean mGrowRecents;
@@ -154,6 +158,7 @@
     private int mExitStartPosition;
     private GestureDetector mGestureDetector;
     private boolean mDockedStackMinimized;
+    private boolean mHomeStackResizable;
     private boolean mAdjustedForIme;
     private DividerState mState;
 
@@ -350,8 +355,9 @@
                 || mStableInsets.bottom != insets.getStableInsetBottom()) {
             mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
                     insets.getStableInsetRight(), insets.getStableInsetBottom());
-            if (mSnapAlgorithm != null) {
+            if (mSnapAlgorithm != null || mMinimizedSnapAlgorithm != null) {
                 mSnapAlgorithm = null;
+                mMinimizedSnapAlgorithm = null;
                 initializeSnapAlgorithm();
             }
         }
@@ -446,11 +452,17 @@
             mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
                     mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
         }
+        if (mMinimizedSnapAlgorithm == null) {
+            mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
+                    mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
+                    mStableInsets, mDockedStackMinimized && mHomeStackResizable);
+        }
     }
 
     public DividerSnapAlgorithm getSnapAlgorithm() {
         initializeSnapAlgorithm();
-        return mSnapAlgorithm;
+        return mDockedStackMinimized && mHomeStackResizable ? mMinimizedSnapAlgorithm :
+                mSnapAlgorithm;
     }
 
     public int getCurrentPosition() {
@@ -495,7 +507,7 @@
                     mMoving = true;
                 }
                 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
-                    SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(
+                    SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget(
                             mStartPosition, 0 /* velocity */, false /* hardDismiss */);
                     resizeStackDelayed(calculatePosition(x, y), mStartPosition, snapTarget);
                 }
@@ -551,9 +563,10 @@
 
     private void fling(int position, float velocity, boolean avoidDismissStart,
             boolean logMetrics) {
-        SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
-        if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
-            snapTarget = mSnapAlgorithm.getFirstSplitTarget();
+        DividerSnapAlgorithm currentSnapAlgorithm = getSnapAlgorithm();
+        SnapTarget snapTarget = currentSnapAlgorithm.calculateSnapTarget(position, velocity);
+        if (avoidDismissStart && snapTarget == currentSnapAlgorithm.getDismissStartTarget()) {
+            snapTarget = currentSnapAlgorithm.getFirstSplitTarget();
         }
         if (logMetrics) {
             logResizeEvent(snapTarget);
@@ -574,6 +587,10 @@
 
     private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget,
             final long endDelay) {
+        if (mCurrentAnimator != null) {
+            cancelFlingAnimation();
+            updateDockSide();
+        }
         final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
         ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
         anim.addUpdateListener(animation -> resizeStackDelayed((int) animation.getAnimatedValue(),
@@ -590,6 +607,12 @@
             mExitAnimationRunning = false;
             EventBus.getDefault().send(new StoppedDragingEvent());
         };
+        Runnable notCancelledEndAction = () -> {
+            // Reset minimized divider position after unminimized state animation finishes
+            if (!mDockedStackMinimized && mIsInMinimizeInteraction) {
+                mIsInMinimizeInteraction = false;
+            }
+        };
         anim.addListener(new AnimatorListenerAdapter() {
 
             private boolean mCancelled;
@@ -612,8 +635,14 @@
                 }
                 if (delay == 0) {
                     endAction.run();
+                    if (!mCancelled) {
+                        notCancelledEndAction.run();
+                    }
                 } else {
                     mHandler.postDelayed(endAction, delay);
+                    if (!mCancelled) {
+                        mHandler.postDelayed(notCancelledEndAction, delay);
+                    }
                 }
             }
         });
@@ -692,57 +721,92 @@
     }
 
 
-    public void setMinimizedDockStack(boolean minimized) {
+    public void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable) {
+        mHomeStackResizable = isHomeStackResizable;
         updateDockSide();
-        mHandle.setAlpha(minimized ? 0f : 1f);
         if (!minimized) {
             resetBackground();
-        } else if (mDockSide == WindowManager.DOCKED_TOP) {
-            mBackground.setPivotY(0);
-            mBackground.setScaleY(MINIMIZE_DOCK_SCALE);
-        } else if (mDockSide == WindowManager.DOCKED_LEFT
-                || mDockSide == WindowManager.DOCKED_RIGHT) {
-            mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
-                    ? 0
-                    : mBackground.getWidth());
-            mBackground.setScaleX(MINIMIZE_DOCK_SCALE);
+        } else if (!isHomeStackResizable) {
+            if (mDockSide == WindowManager.DOCKED_TOP) {
+                mBackground.setPivotY(0);
+                mBackground.setScaleY(MINIMIZE_DOCK_SCALE);
+            } else if (mDockSide == WindowManager.DOCKED_LEFT
+                    || mDockSide == WindowManager.DOCKED_RIGHT) {
+                mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
+                        ? 0
+                        : mBackground.getWidth());
+                mBackground.setScaleX(MINIMIZE_DOCK_SCALE);
+            }
         }
         mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
-        mDockedStackMinimized = minimized;
+        if (!isHomeStackResizable) {
+            mHandle.setAlpha(minimized ? 0f : 1f);
+            mDockedStackMinimized = minimized;
+        } else if (mDockedStackMinimized != minimized) {
+            if (mStableInsets.isEmpty()) {
+                SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
+            }
+            if (!mIsInMinimizeInteraction && minimized) {
+                mIsInMinimizeInteraction = true;
+                mDividerPositionBeforeMinimized = DockedDividerUtils.calculateMiddlePosition(
+                        isHorizontalDivision(), mStableInsets, mDisplayWidth, mDisplayHeight,
+                        mDividerSize);
+            }
+            mMinimizedSnapAlgorithm = null;
+            mDockedStackMinimized = minimized;
+            initializeSnapAlgorithm();
+        }
     }
 
-    public void setMinimizedDockStack(boolean minimized, long animDuration) {
+    public void setMinimizedDockStack(boolean minimized, long animDuration,
+            boolean isHomeStackResizable) {
+        mHomeStackResizable = isHomeStackResizable;
         updateDockSide();
-        mHandle.animate()
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .setDuration(animDuration)
-                .alpha(minimized ? 0f : 1f)
-                .start();
-        if (mDockSide == WindowManager.DOCKED_TOP) {
-            mBackground.setPivotY(0);
-            mBackground.animate()
-                    .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
-        } else if (mDockSide == WindowManager.DOCKED_LEFT
-                || mDockSide == WindowManager.DOCKED_RIGHT) {
-            mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
-                    ? 0
-                    : mBackground.getWidth());
-            mBackground.animate()
-                    .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
+        if (!isHomeStackResizable) {
+            mMinimizedShadow.animate()
+                    .alpha(minimized ? 1f : 0f)
+                    .setInterpolator(Interpolators.ALPHA_IN)
+                    .setDuration(animDuration)
+                    .start();
+            mHandle.animate()
+                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                    .setDuration(animDuration)
+                    .alpha(minimized ? 0f : 1f)
+                    .start();
+            if (mDockSide == WindowManager.DOCKED_TOP) {
+                mBackground.setPivotY(0);
+                mBackground.animate()
+                        .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f);
+            } else if (mDockSide == WindowManager.DOCKED_LEFT
+                    || mDockSide == WindowManager.DOCKED_RIGHT) {
+                mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT
+                        ? 0
+                        : mBackground.getWidth());
+                mBackground.animate()
+                        .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f);
+            }
+            mDockedStackMinimized = minimized;
+        } else if (mDockedStackMinimized != minimized) {
+            mIsInMinimizeInteraction = true;
+            if (minimized) {
+                mDividerPositionBeforeMinimized = getCurrentPosition();
+            }
+            mMinimizedSnapAlgorithm = null;
+            mDockedStackMinimized = minimized;
+            initializeSnapAlgorithm();
+            stopDragging(getCurrentPosition(), minimized ?
+                            mMinimizedSnapAlgorithm.getMiddleTarget() :
+                            mSnapAlgorithm.calculateNonDismissingSnapTarget(
+                                    mDividerPositionBeforeMinimized),
+                    animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
         }
         if (!minimized) {
             mBackground.animate().withEndAction(mResetBackgroundRunnable);
         }
-        mMinimizedShadow.animate()
-                .alpha(minimized ? 1f : 0f)
-                .setInterpolator(Interpolators.ALPHA_IN)
-                .setDuration(animDuration)
-                .start();
         mBackground.animate()
                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                 .setDuration(animDuration)
                 .start();
-        mDockedStackMinimized = minimized;
     }
 
     public void setAdjustedForIme(boolean adjustedForIme) {
@@ -809,6 +873,7 @@
         mDisplayWidth = info.logicalWidth;
         mDisplayHeight = info.logicalHeight;
         mSnapAlgorithm = null;
+        mMinimizedSnapAlgorithm = null;
         initializeSnapAlgorithm();
     }
 
@@ -871,6 +936,15 @@
         }
 
         mLastResizeRect.set(mDockedRect);
+        if (mHomeStackResizable && mIsInMinimizeInteraction) {
+            calculateBoundsForPosition(mDividerPositionBeforeMinimized, mDockSide, mDockedTaskRect);
+            calculateBoundsForPosition(mDividerPositionBeforeMinimized,
+                    DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
+            mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedTaskRect,
+                    mOtherTaskRect, null);
+            return;
+        }
+
         if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
             if (mCurrentAnimator != null) {
                 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
@@ -922,7 +996,7 @@
         } else {
             mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null);
         }
-        SnapTarget closestDismissTarget = mSnapAlgorithm.getClosestDismissTarget(position);
+        SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
         float dimFraction = getDimFraction(position, closestDismissTarget);
         mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
                 getStackIdForDismissTarget(closestDismissTarget),
@@ -943,7 +1017,7 @@
         if (mEntranceAnimationRunning) {
             return 0f;
         }
-        float fraction = mSnapAlgorithm.calculateDismissingFraction(position);
+        float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
         fraction = Math.max(0, Math.min(fraction, 1f));
         fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
         if (hasInsetsAtDismissTarget(dismissTarget)) {
@@ -959,13 +1033,13 @@
      */
     private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
         if (isHorizontalDivision()) {
-            if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
+            if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
                 return mStableInsets.top != 0;
             } else {
                 return mStableInsets.bottom != 0;
             }
         } else {
-            if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) {
+            if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
                 return mStableInsets.left != 0;
             } else {
                 return mStableInsets.right != 0;
@@ -1135,6 +1209,7 @@
         if (mStableInsets.isEmpty()) {
             SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
             mSnapAlgorithm = null;
+            mMinimizedSnapAlgorithm = null;
             initializeSnapAlgorithm();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
deleted file mode 100644
index 9a5e783..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ /dev/null
@@ -1,2588 +0,0 @@
-/*
- * Copyright (C) 2010 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.app.ActivityManager;
-import android.app.ActivityManager.StackId;
-import android.app.ActivityOptions;
-import android.app.INotificationManager;
-import android.app.KeyguardManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.app.TaskStackBuilder;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.content.res.Configuration;
-import android.database.ContentObserver;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.service.dreams.DreamService;
-import android.service.dreams.IDreamManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.view.Display;
-import android.view.IWindowManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.RemoteViews;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardHostView.OnDismissAction;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.RecentsComponent;
-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;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.statusbar.policy.RemoteInputView;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
-import com.android.systemui.util.NotificationChannels;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.Stack;
-
-public abstract class BaseStatusBar extends SystemUI implements
-        CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
-        ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
-        ExpandableNotificationRow.OnExpandClickListener {
-    public static final String TAG = "StatusBar";
-    public static final boolean DEBUG = false;
-    public static final boolean MULTIUSER_DEBUG = false;
-
-    public static final boolean ENABLE_REMOTE_INPUT =
-            SystemProperties.getBoolean("debug.enable_remote_input", true);
-    public static final boolean ENABLE_CHILD_NOTIFICATIONS
-            = SystemProperties.getBoolean("debug.child_notifs", true);
-    public static final boolean FORCE_REMOTE_INPUT_HISTORY =
-            SystemProperties.getBoolean("debug.force_remoteinput_history", false);
-    private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
-
-    protected static final int MSG_SHOW_RECENT_APPS = 1019;
-    protected static final int MSG_HIDE_RECENT_APPS = 1020;
-    protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
-    protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
-    protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
-    protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
-    protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
-    protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
-    protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
-
-    protected static final boolean ENABLE_HEADS_UP = true;
-    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
-
-    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-
-    // Should match the values in PhoneWindowManager
-    public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
-    public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
-
-    private static final String BANNER_ACTION_CANCEL =
-            "com.android.systemui.statusbar.banner_action_cancel";
-    private static final String BANNER_ACTION_SETUP =
-            "com.android.systemui.statusbar.banner_action_setup";
-    private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
-            = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
-
-    protected CommandQueue mCommandQueue;
-    protected IStatusBarService mBarService;
-    protected H mHandler = createHandler();
-
-    // all notifications
-    protected NotificationData mNotificationData;
-    protected NotificationStackScrollLayout mStackScroller;
-
-    protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
-
-    protected RemoteInputController mRemoteInputController;
-
-    // for heads up notifications
-    protected HeadsUpManager mHeadsUpManager;
-
-    // handling reordering
-    protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
-
-    protected int mCurrentUserId = 0;
-    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
-
-    protected int mLayoutDirection = -1; // invalid
-    protected AccessibilityManager mAccessibilityManager;
-
-    protected boolean mDeviceInteractive;
-
-    protected boolean mVisible;
-    protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
-    protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
-
-    /**
-     * Notifications with keys in this set are not actually around anymore. We kept them around
-     * when they were canceled in response to a remote input interaction. This allows us to show
-     * what you replied and allows you to continue typing into it.
-     */
-    protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
-
-    // mScreenOnFromKeyguard && mVisible.
-    private boolean mVisibleToUser;
-
-    private Locale mLocale;
-    private float mFontScale;
-
-    protected boolean mUseHeadsUp = false;
-    protected boolean mHeadsUpTicker = false;
-    protected boolean mDisableNotificationAlerts = false;
-
-    protected DevicePolicyManager mDevicePolicyManager;
-    protected IDreamManager mDreamManager;
-    protected PowerManager mPowerManager;
-    protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-
-    // public mode, private notifications, etc
-    private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
-    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
-    private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
-
-    private UserManager mUserManager;
-    private int mDensity;
-
-    protected KeyguardManager mKeyguardManager;
-    private LockPatternUtils mLockPatternUtils;
-    private DeviceProvisionedController mDeviceProvisionedController;
-
-    // UI-specific methods
-
-    /**
-     * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
-     * and add them to the window manager.
-     */
-    protected abstract void createAndAddWindows();
-
-    protected WindowManager mWindowManager;
-    protected IWindowManager mWindowManagerService;
-
-    protected Display mDisplay;
-
-    protected RecentsComponent mRecents;
-
-    protected int mZenMode;
-
-    // which notification is currently being longpress-examined by the user
-    private NotificationGuts mNotificationGutsExposed;
-    private MenuItem mGutsMenuItem;
-
-    private KeyboardShortcuts mKeyboardShortcuts;
-
-    /**
-     * The {@link StatusBarState} of the status bar.
-     */
-    protected int mState;
-    protected boolean mBouncerShowing;
-    protected boolean mShowLockscreenNotifications;
-    protected boolean mAllowLockscreenRemoteInput;
-
-    protected NotificationShelf mNotificationShelf;
-    protected DismissView mDismissView;
-    protected EmptyShadeView mEmptyShadeView;
-
-    private NotificationClicker mNotificationClicker = new NotificationClicker();
-
-    protected AssistManager mAssistManager;
-
-    protected boolean mVrMode;
-
-    private Set<String> mNonBlockablePkgs;
-
-    @Override  // NotificationData.Environment
-    public boolean isDeviceProvisioned() {
-        return mDeviceProvisionedController.isDeviceProvisioned();
-    }
-
-    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
-        @Override
-        public void onVrStateChanged(boolean enabled) {
-            mVrMode = enabled;
-        }
-    };
-
-    public boolean isDeviceInVrMode() {
-        return mVrMode;
-    }
-
-    private final DeviceProvisionedListener mDeviceProvisionedListener =
-            new DeviceProvisionedListener() {
-        @Override
-        public void onDeviceProvisionedChanged() {
-            updateNotifications();
-        }
-    };
-
-    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            final int mode = Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
-            setZenMode(mode);
-
-            updateLockscreenNotificationSetting();
-        }
-    };
-
-    private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
-        @Override
-        public void onChange(boolean selfChange) {
-            // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
-            // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
-            mUsersAllowingPrivateNotifications.clear();
-            mUsersAllowingNotifications.clear();
-            // ... and refresh all the notifications
-            updateLockscreenNotificationSetting();
-            updateNotifications();
-        }
-    };
-
-    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
-        private final int[] mTmpInt2 = new int[2];
-
-        @Override
-        public boolean onClickHandler(
-                final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
-            view.getLocationInWindow(mTmpInt2);
-            wakeUpIfDozing(SystemClock.uptimeMillis(), new PointF(
-                    mTmpInt2[0] + view.getWidth() / 2, mTmpInt2[1] + view.getHeight() / 2));
-
-
-            if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
-                return true;
-            }
-
-            if (DEBUG) {
-                Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
-            }
-            logActionClick(view);
-            // The intent we are sending is for the application, which
-            // won't have permission to immediately start an activity after
-            // the user switches to home.  We know it is safe to do at this
-            // point, so make sure new activity switches are now allowed.
-            try {
-                ActivityManager.getService().resumeAppSwitches();
-            } catch (RemoteException e) {
-            }
-            final boolean isActivity = pendingIntent.isActivity();
-            if (isActivity) {
-                final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
-                final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
-                        mContext, pendingIntent.getIntent(), mCurrentUserId);
-                dismissKeyguardThenExecute(new OnDismissAction() {
-                    @Override
-                    public boolean onDismiss() {
-                        try {
-                            ActivityManager.getService().resumeAppSwitches();
-                        } catch (RemoteException e) {
-                        }
-
-                        boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
-
-                        // close the shade if it was open
-                        if (handled) {
-                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                    true /* force */);
-                            visibilityChanged(false);
-                            mAssistManager.hideAssist();
-                        }
-
-                        // Wait for activity start.
-                        return handled;
-                    }
-                }, afterKeyguardGone);
-                return true;
-            } else {
-                return superOnClickHandler(view, pendingIntent, fillInIntent);
-            }
-        }
-
-        private void logActionClick(View view) {
-            ViewParent parent = view.getParent();
-            String key = getNotificationKeyForParent(parent);
-            if (key == null) {
-                Log.w(TAG, "Couldn't determine notification for click.");
-                return;
-            }
-            int index = -1;
-            // If this is a default template, determine the index of the button.
-            if (view.getId() == com.android.internal.R.id.action0 &&
-                    parent != null && parent instanceof ViewGroup) {
-                ViewGroup actionGroup = (ViewGroup) parent;
-                index = actionGroup.indexOfChild(view);
-            }
-            try {
-                mBarService.onNotificationActionClick(key, index);
-            } catch (RemoteException e) {
-                // Ignore
-            }
-        }
-
-        private String getNotificationKeyForParent(ViewParent parent) {
-            while (parent != null) {
-                if (parent instanceof ExpandableNotificationRow) {
-                    return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey();
-                }
-                parent = parent.getParent();
-            }
-            return null;
-        }
-
-        private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
-                Intent fillInIntent) {
-            return super.onClickHandler(view, pendingIntent, fillInIntent,
-                    StackId.FULLSCREEN_WORKSPACE_STACK_ID);
-        }
-
-        private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
-            Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
-            RemoteInput[] inputs = null;
-            if (tag instanceof RemoteInput[]) {
-                inputs = (RemoteInput[]) tag;
-            }
-
-            if (inputs == null) {
-                return false;
-            }
-
-            RemoteInput input = null;
-
-            for (RemoteInput i : inputs) {
-                if (i.getAllowFreeFormInput()) {
-                    input = i;
-                }
-            }
-
-            if (input == null) {
-                return false;
-            }
-
-            ViewParent p = view.getParent();
-            RemoteInputView riv = null;
-            while (p != null) {
-                if (p instanceof View) {
-                    View pv = (View) p;
-                    if (pv.isRootNamespace()) {
-                        riv = (RemoteInputView) pv.findViewWithTag(RemoteInputView.VIEW_TAG);
-                        break;
-                    }
-                }
-                p = p.getParent();
-            }
-            ExpandableNotificationRow row = null;
-            while (p != null) {
-                if (p instanceof ExpandableNotificationRow) {
-                    row = (ExpandableNotificationRow) p;
-                    break;
-                }
-                p = p.getParent();
-            }
-
-            if (riv == null || row == null) {
-                return false;
-            }
-
-            row.setUserExpanded(true);
-
-            if (!mAllowLockscreenRemoteInput) {
-                final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
-                if (isLockscreenPublicMode(userId)) {
-                    onLockedRemoteInput(row, view);
-                    return true;
-                }
-                if (mUserManager.getUserInfo(userId).isManagedProfile()
-                        && mKeyguardManager.isDeviceLocked(userId)) {
-                    onLockedWorkRemoteInput(userId, row, view);
-                    return true;
-                }
-            }
-
-            int width = view.getWidth();
-            if (view instanceof TextView) {
-                // Center the reveal on the text which might be off-center from the TextView
-                TextView tv = (TextView) view;
-                if (tv.getLayout() != null) {
-                    int innerWidth = (int) tv.getLayout().getLineWidth(0);
-                    innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
-                    width = Math.min(width, innerWidth);
-                }
-            }
-            int cx = view.getLeft() + width / 2;
-            int cy = view.getTop() + view.getHeight() / 2;
-            int w = riv.getWidth();
-            int h = riv.getHeight();
-            int r = Math.max(
-                    Math.max(cx + cy, cx + (h - cy)),
-                    Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
-            riv.setRevealParameters(cx, cy, r);
-            riv.setPendingIntent(pendingIntent);
-            riv.setRemoteInput(inputs, input);
-            riv.focusAnimated();
-
-            return true;
-        }
-
-    };
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                updateCurrentProfilesCache();
-                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
-
-                updateLockscreenNotificationSetting();
-
-                userSwitched(mCurrentUserId);
-            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
-                updateCurrentProfilesCache();
-            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                List<ActivityManager.RecentTaskInfo> recentTask = null;
-                try {
-                    recentTask = ActivityManager.getService().getRecentTasks(1,
-                            ActivityManager.RECENT_WITH_EXCLUDED
-                            | ActivityManager.RECENT_INCLUDE_PROFILES,
-                            mCurrentUserId).getList();
-                } catch (RemoteException e) {
-                    // Abandon hope activity manager not running.
-                }
-                if (recentTask != null && recentTask.size() > 0) {
-                    UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId);
-                    if (user != null && user.isManagedProfile()) {
-                        Toast toast = Toast.makeText(mContext,
-                                R.string.managed_profile_foreground_toast,
-                                Toast.LENGTH_SHORT);
-                        TextView text = (TextView) toast.getView().findViewById(
-                                android.R.id.message);
-                        text.setCompoundDrawablesRelativeWithIntrinsicBounds(
-                                R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
-                        int paddingPx = mContext.getResources().getDimensionPixelSize(
-                                R.dimen.managed_profile_toast_padding);
-                        text.setCompoundDrawablePadding(paddingPx);
-                        toast.show();
-                    }
-                }
-            } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
-                NotificationManager noMan = (NotificationManager)
-                        mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-                noMan.cancel(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS);
-
-                Settings.Secure.putInt(mContext.getContentResolver(),
-                        Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
-                if (BANNER_ACTION_SETUP.equals(action)) {
-                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */);
-                    mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
-                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-
-                    );
-                }
-            } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
-                final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
-                final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
-                if (intentSender != null) {
-                    try {
-                        mContext.startIntentSender(intentSender, null, 0, 0, 0);
-                    } catch (IntentSender.SendIntentException e) {
-                        /* ignore */
-                    }
-                }
-                if (notificationKey != null) {
-                    try {
-                        mBarService.onNotificationClick(notificationKey);
-                    } catch (RemoteException e) {
-                        /* ignore */
-                    }
-                }
-            }
-        }
-    };
-
-    private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-
-            if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
-                    isCurrentProfile(getSendingUserId())) {
-                mUsersAllowingPrivateNotifications.clear();
-                updateLockscreenNotificationSetting();
-                updateNotifications();
-            } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
-                if (userId != mCurrentUserId && isCurrentProfile(userId)) {
-                    onWorkChallengeChanged();
-                }
-            }
-        }
-    };
-
-    private final NotificationListenerService mNotificationListener =
-            new NotificationListenerService() {
-        @Override
-        public void onListenerConnected() {
-            if (DEBUG) Log.d(TAG, "onListenerConnected");
-            final StatusBarNotification[] notifications = getActiveNotifications();
-            if (notifications == null) {
-                Log.w(TAG, "onListenerConnected unable to get active notifications.");
-                return;
-            }
-            final RankingMap currentRanking = getCurrentRanking();
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    for (StatusBarNotification sbn : notifications) {
-                        addNotification(sbn, currentRanking, null /* oldEntry */);
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onNotificationPosted(final StatusBarNotification sbn,
-                final RankingMap rankingMap) {
-            if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
-            if (sbn != null) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        processForRemoteInput(sbn.getNotification());
-                        String key = sbn.getKey();
-                        mKeysKeptForRemoteInput.remove(key);
-                        boolean isUpdate = mNotificationData.get(key) != null;
-                        // In case we don't allow child notifications, we ignore children of
-                        // notifications that have a summary, since we're not going to show them
-                        // anyway. This is true also when the summary is canceled,
-                        // because children are automatically canceled by NoMan in that case.
-                        if (!ENABLE_CHILD_NOTIFICATIONS
-                            && mGroupManager.isChildInGroupWithSummary(sbn)) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
-                            }
-
-                            // Remove existing notification to avoid stale data.
-                            if (isUpdate) {
-                                removeNotification(key, rankingMap);
-                            } else {
-                                mNotificationData.updateRanking(rankingMap);
-                            }
-                            return;
-                        }
-                        if (isUpdate) {
-                            updateNotification(sbn, rankingMap);
-                        } else {
-                            addNotification(sbn, rankingMap, null /* oldEntry */);
-                        }
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onNotificationRemoved(StatusBarNotification sbn,
-                final RankingMap rankingMap) {
-            if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
-            if (sbn != null) {
-                final String key = sbn.getKey();
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        removeNotification(key, rankingMap);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void onNotificationRankingUpdate(final RankingMap rankingMap) {
-            if (DEBUG) Log.d(TAG, "onRankingUpdate");
-            if (rankingMap != null) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    updateNotificationRanking(rankingMap);
-                }
-            });
-        }                            }
-
-    };
-
-    private void updateCurrentProfilesCache() {
-        synchronized (mCurrentProfiles) {
-            mCurrentProfiles.clear();
-            if (mUserManager != null) {
-                for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
-                    mCurrentProfiles.put(user.id, user);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void start() {
-        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
-        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
-        mDisplay = mWindowManager.getDefaultDisplay();
-        mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService(
-                Context.DEVICE_POLICY_SERVICE);
-
-        mNotificationData = new NotificationData(this);
-
-        mAccessibilityManager = (AccessibilityManager)
-                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
-
-        mDreamManager = IDreamManager.Stub.asInterface(
-                ServiceManager.checkService(DreamService.DREAM_SERVICE));
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-
-        mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
-                mSettingsObserver);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
-                mLockscreenSettingsObserver,
-                UserHandle.USER_ALL);
-        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
-                    false,
-                    mSettingsObserver,
-                    UserHandle.USER_ALL);
-        }
-
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
-                true,
-                mLockscreenSettingsObserver,
-                UserHandle.USER_ALL);
-
-        mBarService = IStatusBarService.Stub.asInterface(
-                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-
-        mRecents = getComponent(Recents.class);
-
-        final Configuration currentConfig = mContext.getResources().getConfiguration();
-        mLocale = currentConfig.locale;
-        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
-        mFontScale = currentConfig.fontScale;
-        mDensity = currentConfig.densityDpi;
-
-        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mLockPatternUtils = new LockPatternUtils(mContext);
-
-        // Connect in to the status bar manager service
-        mCommandQueue = getComponent(CommandQueue.class);
-        mCommandQueue.addCallbacks(this);
-
-        int[] switches = new int[9];
-        ArrayList<IBinder> binders = new ArrayList<IBinder>();
-        ArrayList<String> iconSlots = new ArrayList<>();
-        ArrayList<StatusBarIcon> icons = new ArrayList<>();
-        Rect fullscreenStackBounds = new Rect();
-        Rect dockedStackBounds = new Rect();
-        try {
-            mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
-                    fullscreenStackBounds, dockedStackBounds);
-        } catch (RemoteException ex) {
-            // If the system process isn't there we're doomed anyway.
-        }
-
-        createAndAddWindows();
-
-        mSettingsObserver.onChange(false); // set up
-        disable(switches[0], switches[6], false /* animate */);
-        setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
-                fullscreenStackBounds, dockedStackBounds);
-        topAppWindowChanged(switches[2] != 0);
-        // StatusBarManagerService has a back up of IME token and it's restored here.
-        setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0);
-
-        // Set up the initial icon state
-        int N = iconSlots.size();
-        int viewIndex = 0;
-        for (int i=0; i < N; i++) {
-            setIcon(iconSlots.get(i), icons.get(i));
-        }
-
-        // Set up the initial notification state.
-        try {
-            mNotificationListener.registerAsSystemService(mContext,
-                    new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
-                    UserHandle.USER_ALL);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to register notification listener", e);
-        }
-
-
-        if (DEBUG) {
-            Log.d(TAG, String.format(
-                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
-                   icons.size(),
-                   switches[0],
-                   switches[1],
-                   switches[2],
-                   switches[3]
-                   ));
-        }
-
-        mCurrentUserId = ActivityManager.getCurrentUser();
-        setHeadsUpUser(mCurrentUserId);
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        filter.addAction(Intent.ACTION_USER_ADDED);
-        filter.addAction(Intent.ACTION_USER_PRESENT);
-        mContext.registerReceiver(mBroadcastReceiver, filter);
-
-        IntentFilter internalFilter = new IntentFilter();
-        internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
-        internalFilter.addAction(BANNER_ACTION_CANCEL);
-        internalFilter.addAction(BANNER_ACTION_SETUP);
-        mContext.registerReceiver(mBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
-
-        IntentFilter allUsersFilter = new IntentFilter();
-        allUsersFilter.addAction(
-                DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
-        allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
-        mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
-                null, null);
-        updateCurrentProfilesCache();
-
-        IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService("vrmanager"));
-        try {
-            vrManager.registerListener(mVrStateCallbacks);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to register VR mode state listener: " + e);
-        }
-
-        mNonBlockablePkgs = new HashSet<String>();
-        Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_nonBlockableNotificationPackages));
-    }
-
-    protected void notifyUserAboutHiddenNotifications() {
-        if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) {
-            Log.d(TAG, "user hasn't seen notification about hidden notifications");
-            if (!mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
-                Log.d(TAG, "insecure lockscreen, skipping notification");
-                Settings.Secure.putInt(mContext.getContentResolver(),
-                        Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
-                return;
-            }
-            Log.d(TAG, "disabling lockecreen notifications and alerting the user");
-            // disable lockscreen notifications until user acts on the banner.
-            Settings.Secure.putInt(mContext.getContentResolver(),
-                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
-            Settings.Secure.putInt(mContext.getContentResolver(),
-                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0);
-
-            final String packageName = mContext.getPackageName();
-            PendingIntent cancelIntent = PendingIntent.getBroadcast(mContext, 0,
-                    new Intent(BANNER_ACTION_CANCEL).setPackage(packageName),
-                    PendingIntent.FLAG_CANCEL_CURRENT);
-            PendingIntent setupIntent = PendingIntent.getBroadcast(mContext, 0,
-                    new Intent(BANNER_ACTION_SETUP).setPackage(packageName),
-                    PendingIntent.FLAG_CANCEL_CURRENT);
-
-            final int colorRes = com.android.internal.R.color.system_notification_accent_color;
-            Notification.Builder note = new Notification.Builder(mContext)
-                    .setSmallIcon(R.drawable.ic_android)
-                    .setContentTitle(mContext.getString(R.string.hidden_notifications_title))
-                    .setContentText(mContext.getString(R.string.hidden_notifications_text))
-                    .setChannel(NotificationChannels.SECURITY)
-                    .setOngoing(true)
-                    .setColor(mContext.getColor(colorRes))
-                    .setContentIntent(setupIntent)
-                    .addAction(R.drawable.ic_close,
-                            mContext.getString(R.string.hidden_notifications_cancel),
-                            cancelIntent)
-                    .addAction(R.drawable.ic_settings,
-                            mContext.getString(R.string.hidden_notifications_setup),
-                            setupIntent);
-            overrideNotificationAppName(mContext, note);
-
-            NotificationManager noMan =
-                    (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-            noMan.notify(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS, note.build());
-        }
-    }
-
-    public void userSwitched(int newUserId) {
-        setHeadsUpUser(newUserId);
-    }
-
-    protected abstract void setHeadsUpUser(int newUserId);
-
-    @Override  // NotificationData.Environment
-    public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
-        final int thisUserId = mCurrentUserId;
-        final int notificationUserId = n.getUserId();
-        if (DEBUG && MULTIUSER_DEBUG) {
-            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
-                    n, thisUserId, notificationUserId));
-        }
-        return isCurrentProfile(notificationUserId);
-    }
-
-    protected void setNotificationShown(StatusBarNotification n) {
-        setNotificationsShown(new String[]{n.getKey()});
-    }
-
-    protected void setNotificationsShown(String[] keys) {
-        try {
-            mNotificationListener.setNotificationsShown(keys);
-        } catch (RuntimeException e) {
-            Log.d(TAG, "failed setNotificationsShown: ", e);
-        }
-    }
-
-    protected boolean isCurrentProfile(int userId) {
-        synchronized (mCurrentProfiles) {
-            return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
-        }
-    }
-
-    @Override
-    public String getCurrentMediaNotificationKey() {
-        return null;
-    }
-
-    @Override
-    public NotificationGroupManager getGroupManager() {
-        return mGroupManager;
-    }
-
-    /**
-     * Takes the necessary steps to prepare the status bar for starting an activity, then starts it.
-     * @param action A dismiss action that is called if it's safe to start the activity.
-     * @param afterKeyguardGone Whether the action should be executed after the Keyguard is gone.
-     */
-    protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) {
-        action.onDismiss();
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        final float fontScale = newConfig.fontScale;
-        final int density = newConfig.densityDpi;
-        if (density != mDensity || mFontScale != fontScale) {
-            onDensityOrFontScaleChanged();
-            mDensity = density;
-            mFontScale = fontScale;
-        }
-    }
-
-    protected void onDensityOrFontScaleChanged() {
-        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-        for (int i = 0; i < activeNotifications.size(); i++) {
-            Entry entry = activeNotifications.get(i);
-            boolean exposedGuts = entry.row.getGuts() == mNotificationGutsExposed;
-            entry.row.reInflateViews();
-            if (exposedGuts) {
-                mNotificationGutsExposed = entry.row.getGuts();
-                bindGuts(entry.row, mGutsMenuItem);
-            }
-            inflateViews(entry, mStackScroller);
-        }
-    }
-
-    protected void bindDismissRunnable(final ExpandableNotificationRow row) {
-        row.setOnDismissRunnable(() -> performRemoveNotification(row.getStatusBarNotification()));
-    }
-
-    protected void performRemoveNotification(StatusBarNotification n) {
-        final String pkg = n.getPackageName();
-        final String tag = n.getTag();
-        final int id = n.getId();
-        final int userId = n.getUserId();
-        try {
-            mBarService.onNotificationClear(pkg, tag, id, userId);
-            if (FORCE_REMOTE_INPUT_HISTORY
-                    && mKeysKeptForRemoteInput.contains(n.getKey())) {
-                mKeysKeptForRemoteInput.remove(n.getKey());
-            }
-            removeNotification(n.getKey(), null);
-
-        } catch (RemoteException ex) {
-            // system process is dead if we're here.
-        }
-    }
-
-
-    protected void applyColorsAndBackgrounds(StatusBarNotification sbn,
-            NotificationData.Entry entry) {
-
-        if (entry.getContentView().getId()
-                != com.android.internal.R.id.status_bar_latest_event_content) {
-            // Using custom RemoteViews
-            if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
-                    && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) {
-                entry.row.setShowingLegacyBackground(true);
-                entry.legacy = true;
-            }
-        }
-
-        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-    }
-
-    public boolean isMediaNotification(NotificationData.Entry entry) {
-        // TODO: confirm that there's a valid media key
-        return entry.getExpandedContentView() != null &&
-               entry.getExpandedContentView()
-                       .findViewById(com.android.internal.R.id.media_actions) != null;
-    }
-
-    // The (i) button in the guts that links to the system notification settings for that app
-    private void startAppNotificationSettingsActivity(String packageName, final int appUid) {
-        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
-        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
-        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
-        startNotificationGutsIntent(intent, appUid);
-    }
-
-    private void startNotificationGutsIntent(final Intent intent, final int appUid) {
-        dismissKeyguardThenExecute(new OnDismissAction() {
-            @Override
-            public boolean onDismiss() {
-                AsyncTask.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        TaskStackBuilder.create(mContext)
-                                .addNextIntentWithParentStack(intent)
-                                .startActivities(getActivityOptions(),
-                                        new UserHandle(UserHandle.getUserId(appUid)));
-                    }
-                });
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-                return true;
-            }
-        }, false /* afterKeyguardGone */);
-    }
-
-    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();
-        row.setTag(sbn.getPackageName());
-        final NotificationGuts guts = row.getGuts();
-        guts.setClosedListener((NotificationGuts g) -> {
-            if (!row.isRemoved()) {
-                mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
-            }
-            mNotificationGutsExposed = null;
-        });
-
-        if (item.gutsContent instanceof SnoozeGutsContent) {
-            ((SnoozeGutsContent) item.gutsContent).setSnoozeListener(getSnoozeListener());
-            ((SnoozeGutsContent) item.gutsContent).setStatusBarNotification(sbn);
-        }
-
-        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 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);
-        done.getLocationOnScreen(doneLocation);
-
-        final int centerX = done.getWidth() / 2;
-        final int centerY = done.getHeight() / 2;
-        final int x = doneLocation[0] - rowLocation[0] + centerX;
-        final int y = doneLocation[1] - rowLocation[1] + centerY;
-        dismissPopups(x, y);
-    }
-
-    protected SwipeHelper.LongPressListener getNotificationLongClicker() {
-        return new SwipeHelper.LongPressListener() {
-            @Override
-            public boolean onLongPress(View v, final int x, final int y,
-                    MenuItem item) {
-                if (!(v instanceof ExpandableNotificationRow)) {
-                    return false;
-                }
-                if (v.getWindowToken() == null) {
-                    Log.e(TAG, "Trying to show notification guts, but not attached to window");
-                    return false;
-                }
-
-                final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-                bindGuts(row, item);
-                NotificationGuts guts = row.getGuts();
-
-                // Assume we are a status_bar_notification_row
-                if (guts == null) {
-                    // This view has no guts. Examples are the more card or the dismiss all view
-                    return false;
-                }
-
-                // Already showing?
-                if (guts.getVisibility() == View.VISIBLE) {
-                    dismissPopups(x, y);
-                    return false;
-                }
-
-                MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_CONTROLS);
-
-                // ensure that it's laid but not visible until actually laid out
-                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 "
-                                    + "window");
-                            return;
-                        }
-                        dismissPopups(-1 /* x */, -1 /* y */, false /* resetGear */,
-                                false /* animate */);
-                        guts.setVisibility(View.VISIBLE);
-                        final double horz = Math.max(guts.getWidth() - x, x);
-                        final double vert = Math.max(guts.getHeight() - y, y);
-                        final float r = (float) Math.hypot(horz, vert);
-                        final Animator a
-                                = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
-                        a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                        a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-                        a.addListener(new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationEnd(Animator animation) {
-                                super.onAnimationEnd(animation);
-                                // Move the notification view back over the gear
-                                row.resetTranslation();
-                            }
-                        });
-                        a.start();
-                        guts.setExposed(true /* exposed */,
-                                mState == StatusBarState.KEYGUARD /* needsFalsingProtection */);
-                        row.closeRemoteInput();
-                        mStackScroller.onHeightChanged(row, true /* needsAnimation */);
-                        mNotificationGutsExposed = guts;
-                        mGutsMenuItem = item;
-                    }
-                });
-                return true;
-            }
-        };
-    }
-
-    /**
-     * Returns the exposed NotificationGuts or null if none are exposed.
-     */
-    public NotificationGuts getExposedGuts() {
-        return mNotificationGutsExposed;
-    }
-
-    public void dismissPopups() {
-        dismissPopups(-1 /* x */, -1 /* y */, true /* resetGear */, false /* animate */);
-    }
-
-    private void dismissPopups(int x, int y) {
-        dismissPopups(x, y, true /* resetGear */, false /* animate */);
-    }
-
-    public void dismissPopups(int x, int y, boolean resetGear, boolean animate) {
-        if (mNotificationGutsExposed != null) {
-            mNotificationGutsExposed.closeControls(x, y, true /* save */);
-        }
-        if (resetGear) {
-            mStackScroller.resetExposedGearView(animate, true /* force */);
-        }
-    }
-
-    @Override
-    public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) {
-        int msg = MSG_SHOW_RECENT_APPS;
-        mHandler.removeMessages(msg);
-        mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, fromHome ? 1 : 0).sendToTarget();
-    }
-
-    @Override
-    public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-        int msg = MSG_HIDE_RECENT_APPS;
-        mHandler.removeMessages(msg);
-        mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0,
-                triggeredFromHomeKey ? 1 : 0).sendToTarget();
-    }
-
-    @Override
-    public void toggleRecentApps() {
-        toggleRecents();
-    }
-
-    @Override
-    public void toggleSplitScreen() {
-        toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
-    }
-
-    @Override
-    public void preloadRecentApps() {
-        int msg = MSG_PRELOAD_RECENT_APPS;
-        mHandler.removeMessages(msg);
-        mHandler.sendEmptyMessage(msg);
-    }
-
-    @Override
-    public void cancelPreloadRecentApps() {
-        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
-        mHandler.removeMessages(msg);
-        mHandler.sendEmptyMessage(msg);
-    }
-
-    @Override
-    public void dismissKeyboardShortcutsMenu() {
-        int msg = MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU;
-        mHandler.removeMessages(msg);
-        mHandler.sendEmptyMessage(msg);
-    }
-
-    @Override
-    public void toggleKeyboardShortcutsMenu(int deviceId) {
-        int msg = MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU;
-        mHandler.removeMessages(msg);
-        mHandler.obtainMessage(msg, deviceId, 0).sendToTarget();
-    }
-
-    /** Jumps to the next affiliated task in the group. */
-    public void showNextAffiliatedTask() {
-        int msg = MSG_SHOW_NEXT_AFFILIATED_TASK;
-        mHandler.removeMessages(msg);
-        mHandler.sendEmptyMessage(msg);
-    }
-
-    /** Jumps to the previous affiliated task in the group. */
-    public void showPreviousAffiliatedTask() {
-        int msg = MSG_SHOW_PREV_AFFILIATED_TASK;
-        mHandler.removeMessages(msg);
-        mHandler.sendEmptyMessage(msg);
-    }
-
-    protected H createHandler() {
-         return new H();
-    }
-
-    protected void sendCloseSystemWindows(String reason) {
-        try {
-            ActivityManager.getService().closeSystemDialogs(reason);
-        } catch (RemoteException e) {
-        }
-    }
-
-    protected abstract View getStatusBarView();
-
-    /**
-     * Toggle docking the app window
-     *
-     * @param metricsDockAction the action to log when docking is successful, or -1 to not log
-     *                          anything on successful docking
-     * @param metricsUndockAction the action to log when undocking, or -1 to not log anything when
-     *                            undocking
-     * @return true if toggle split screen was successful
-     */
-    protected abstract boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction);
-
-    /** Proxy for RecentsComponent */
-
-    protected void showRecents(boolean triggeredFromAltTab, boolean fromHome) {
-        if (mRecents != null) {
-            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
-            mRecents.showRecents(triggeredFromAltTab, fromHome);
-        }
-    }
-
-    protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-        if (mRecents != null) {
-            mRecents.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
-        }
-    }
-
-    protected void toggleRecents() {
-        if (mRecents != null) {
-            mRecents.toggleRecents(mDisplay);
-        }
-    }
-
-    protected void preloadRecents() {
-        if (mRecents != null) {
-            mRecents.preloadRecents();
-        }
-    }
-
-    protected void toggleKeyboardShortcuts(int deviceId) {
-        KeyboardShortcuts.toggle(mContext, deviceId);
-    }
-
-    protected void dismissKeyboardShortcuts() {
-        KeyboardShortcuts.dismiss();
-    }
-
-    protected void cancelPreloadingRecents() {
-        if (mRecents != null) {
-            mRecents.cancelPreloadingRecents();
-        }
-    }
-
-    protected void showRecentsNextAffiliatedTask() {
-        if (mRecents != null) {
-            mRecents.showNextAffiliatedTask();
-        }
-    }
-
-    protected void showRecentsPreviousAffiliatedTask() {
-        if (mRecents != null) {
-            mRecents.showPrevAffiliatedTask();
-        }
-    }
-
-    /**
-     * If there is an active heads-up notification and it has a fullscreen intent, fire it now.
-     */
-    public abstract void maybeEscalateHeadsUp();
-
-    /**
-     * Save the current "public" (locked and secure) state of the lockscreen.
-     */
-    public void setLockscreenPublicMode(boolean publicMode, int userId) {
-        mLockscreenPublicMode.put(userId, publicMode);
-    }
-
-    public boolean isLockscreenPublicMode(int userId) {
-        return mLockscreenPublicMode.get(userId, false);
-    }
-
-    protected void onWorkChallengeChanged() {}
-
-    /**
-     * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
-     * "public" (secure & locked) mode?
-     */
-    public boolean userAllowsNotificationsInPublic(int userHandle) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
-
-        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowed = 0 != Settings.Secure.getIntForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
-            mUsersAllowingNotifications.append(userHandle, allowed);
-            return allowed;
-        }
-
-        return mUsersAllowingNotifications.get(userHandle);
-    }
-
-    /**
-     * Has the given user chosen to allow their private (full) notifications to be shown even
-     * when the lockscreen is in "public" (secure & locked) mode?
-     */
-    public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
-
-        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
-            final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
-            final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle);
-            final boolean allowed = allowedByUser && allowedByDpm;
-            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
-            return allowed;
-        }
-
-        return mUsersAllowingPrivateNotifications.get(userHandle);
-    }
-
-    private boolean adminAllowsUnredactedNotifications(int userHandle) {
-        if (userHandle == UserHandle.USER_ALL) {
-            return true;
-        }
-        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
-                    userHandle);
-        return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
-    }
-
-    /**
-     * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
-     * If so, notifications should be hidden.
-     */
-    @Override  // NotificationData.Environment
-    public boolean shouldHideNotifications(int userId) {
-        return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
-                || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
-    }
-
-    /**
-     * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
-     * package-specific override.
-     */
-    @Override // NotificationDate.Environment
-    public boolean shouldHideNotifications(String key) {
-        return isLockscreenPublicMode(mCurrentUserId)
-                && mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET;
-    }
-
-    /**
-     * Returns true if we're on a secure lockscreen.
-     */
-    @Override  // NotificationData.Environment
-    public boolean isSecurelyLocked(int userId) {
-        return isLockscreenPublicMode(userId);
-    }
-
-    public void onNotificationClear(StatusBarNotification notification) {
-        try {
-            mBarService.onNotificationClear(
-                    notification.getPackageName(),
-                    notification.getTag(),
-                    notification.getId(),
-                    notification.getUserId());
-        } catch (android.os.RemoteException ex) {
-            // oh well
-        }
-    }
-
-    /**
-     * Called when the notification panel layouts
-     */
-    public void onPanelLaidOut() {
-        if (mState == StatusBarState.KEYGUARD) {
-            // Since the number of notifications is determined based on the height of the view, we
-            // need to update them.
-            int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
-            int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
-            if (maxBefore != maxNotifications) {
-                updateRowStates();
-            }
-        }
-    }
-
-    protected void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {}
-
-    protected void onLockedRemoteInput(ExpandableNotificationRow row, View clickedView) {}
-
-    protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
-            View clicked) {}
-
-    @Override
-    public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
-    }
-
-    protected class H extends Handler {
-        @Override
-        public void handleMessage(Message m) {
-            switch (m.what) {
-             case MSG_SHOW_RECENT_APPS:
-                 showRecents(m.arg1 > 0, m.arg2 != 0);
-                 break;
-             case MSG_HIDE_RECENT_APPS:
-                 hideRecents(m.arg1 > 0, m.arg2 > 0);
-                 break;
-             case MSG_TOGGLE_RECENTS_APPS:
-                 toggleRecents();
-                 break;
-             case MSG_PRELOAD_RECENT_APPS:
-                  preloadRecents();
-                  break;
-             case MSG_CANCEL_PRELOAD_RECENT_APPS:
-                  cancelPreloadingRecents();
-                  break;
-             case MSG_SHOW_NEXT_AFFILIATED_TASK:
-                  showRecentsNextAffiliatedTask();
-                  break;
-             case MSG_SHOW_PREV_AFFILIATED_TASK:
-                  showRecentsPreviousAffiliatedTask();
-                  break;
-             case MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU:
-                  toggleKeyboardShortcuts(m.arg1);
-                  break;
-             case MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU:
-                  dismissKeyboardShortcuts();
-                  break;
-            }
-        }
-    }
-
-    protected void workAroundBadLayerDrawableOpacity(View v) {
-    }
-
-    protected boolean inflateViews(Entry entry, ViewGroup parent) {
-        PackageManager pmUser = getPackageManagerForUser(mContext,
-                entry.notification.getUser().getIdentifier());
-
-        final StatusBarNotification sbn = entry.notification;
-        boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
-        try {
-            entry.cacheContentViews(mContext, null, isLowPriority);
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Unable to get notification remote views", e);
-            return false;
-        }
-
-        final RemoteViews contentView = entry.cachedContentView;
-        final RemoteViews bigContentView = entry.cachedBigContentView;
-        final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
-        final RemoteViews publicContentView = entry.cachedPublicContentView;
-        final RemoteViews ambientContentView = entry.cachedAmbientContentView;
-
-        if (contentView == null) {
-            Log.v(TAG, "no contentView for: " + sbn.getNotification());
-            return false;
-        }
-
-        if (DEBUG) {
-            Log.v(TAG, "publicContentView: " + publicContentView);
-        }
-
-        ExpandableNotificationRow row;
-
-        // Stash away previous user expansion state so we can restore it at
-        // the end.
-        boolean hasUserChangedExpansion = false;
-        boolean userExpanded = false;
-        boolean userLocked = false;
-
-        if (entry.row != null) {
-            row = entry.row;
-            hasUserChangedExpansion = row.hasUserChangedExpansion();
-            userExpanded = row.isUserExpanded();
-            userLocked = row.isUserLocked();
-            entry.reset();
-            if (hasUserChangedExpansion) {
-                row.setUserExpanded(userExpanded);
-            }
-        } else {
-            // create the row view
-            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
-                    Context.LAYOUT_INFLATER_SERVICE);
-            row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
-                    parent, false);
-            row.setExpansionLogger(this, entry.notification.getKey());
-            row.setGroupManager(mGroupManager);
-            row.setHeadsUpManager(mHeadsUpManager);
-            row.setRemoteInputController(mRemoteInputController);
-            row.setOnExpandClickListener(this);
-
-            // Get the app name.
-            // Note that Notification.Builder#bindHeaderAppName has similar logic
-            // but since this field is used in the guts, it must be accurate.
-            // Therefore we will only show the application label, or, failing that, the
-            // package name. No substitutions.
-            final String pkg = sbn.getPackageName();
-            String appname = pkg;
-            try {
-                final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
-                        PackageManager.MATCH_UNINSTALLED_PACKAGES
-                                | PackageManager.MATCH_DISABLED_COMPONENTS);
-                if (info != null) {
-                    appname = String.valueOf(pmUser.getApplicationLabel(info));
-                }
-            } catch (NameNotFoundException e) {
-                // Do nothing
-            }
-            row.setAppName(appname);
-        }
-
-        workAroundBadLayerDrawableOpacity(row);
-        bindDismissRunnable(row);
-        row.setIsLowPriority(isLowPriority);
-
-        // NB: the large icon is now handled entirely by the template
-
-        // bind the click event to the content area
-        NotificationContentView contentContainer = row.getPrivateLayout();
-        NotificationContentView contentContainerPublic = row.getPublicLayout();
-
-        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-        if (ENABLE_REMOTE_INPUT) {
-            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
-        }
-
-        mNotificationClicker.register(row, sbn);
-
-        // set up the adaptive layout
-        View contentViewLocal = null;
-        View bigContentViewLocal = null;
-        View headsUpContentViewLocal = null;
-        View publicViewLocal = null;
-        View ambientViewLocal = null;
-        try {
-            contentViewLocal = contentView.apply(
-                    sbn.getPackageContext(mContext),
-                    contentContainer,
-                    mOnClickHandler);
-            if (bigContentView != null) {
-                bigContentViewLocal = bigContentView.apply(
-                        sbn.getPackageContext(mContext),
-                        contentContainer,
-                        mOnClickHandler);
-            }
-            if (headsUpContentView != null) {
-                headsUpContentViewLocal = headsUpContentView.apply(
-                        sbn.getPackageContext(mContext),
-                        contentContainer,
-                        mOnClickHandler);
-            }
-            if (publicContentView != null) {
-                publicViewLocal = publicContentView.apply(
-                        sbn.getPackageContext(mContext),
-                        contentContainerPublic, mOnClickHandler);
-            }
-            if (ambientContentView != null) {
-                ambientViewLocal = ambientContentView.apply(
-                        sbn.getPackageContext(mContext),
-                        contentContainer, mOnClickHandler);
-            }
-
-            if (contentViewLocal != null) {
-                contentViewLocal.setIsRootNamespace(true);
-                contentContainer.setContractedChild(contentViewLocal);
-            }
-            if (bigContentViewLocal != null) {
-                bigContentViewLocal.setIsRootNamespace(true);
-                contentContainer.setExpandedChild(bigContentViewLocal);
-            }
-            if (headsUpContentViewLocal != null) {
-                headsUpContentViewLocal.setIsRootNamespace(true);
-                contentContainer.setHeadsUpChild(headsUpContentViewLocal);
-            }
-            if (publicViewLocal != null) {
-                publicViewLocal.setIsRootNamespace(true);
-                contentContainerPublic.setContractedChild(publicViewLocal);
-            }
-
-            if (ambientViewLocal != null) {
-                ambientViewLocal.setIsRootNamespace(true);
-                contentContainer.setAmbientChild(ambientViewLocal);
-            }
-        }
-        catch (RuntimeException e) {
-            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
-            Log.e(TAG, "couldn't inflate view for notification " + ident, e);
-            return false;
-        }
-
-        // Extract target SDK version.
-        try {
-            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
-            entry.targetSdk = info.targetSdkVersion;
-        } catch (NameNotFoundException ex) {
-            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
-        }
-        entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
-
-        entry.row = row;
-        entry.row.setOnActivatedListener(this);
-        entry.row.setExpandable(bigContentViewLocal != null);
-
-        applyColorsAndBackgrounds(sbn, entry);
-
-        // Restore previous flags.
-        if (hasUserChangedExpansion) {
-            // Note: setUserExpanded() conveniently ignores calls with
-            //       userExpanded=true if !isExpandable().
-            row.setUserExpanded(userExpanded);
-        }
-        row.setUserLocked(userLocked);
-        row.onNotificationUpdated(entry);
-        return true;
-    }
-
-    /**
-     * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
-     * via first-class API.
-     *
-     * TODO: Remove once enough apps specify remote inputs on their own.
-     */
-    private void processForRemoteInput(Notification n) {
-        if (!ENABLE_REMOTE_INPUT) return;
-
-        if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
-                (n.actions == null || n.actions.length == 0)) {
-            Notification.Action viableAction = null;
-            Notification.WearableExtender we = new Notification.WearableExtender(n);
-
-            List<Notification.Action> actions = we.getActions();
-            final int numActions = actions.size();
-
-            for (int i = 0; i < numActions; i++) {
-                Notification.Action action = actions.get(i);
-                if (action == null) {
-                    continue;
-                }
-                RemoteInput[] remoteInputs = action.getRemoteInputs();
-                if (remoteInputs == null) {
-                    continue;
-                }
-                for (RemoteInput ri : remoteInputs) {
-                    if (ri.getAllowFreeFormInput()) {
-                        viableAction = action;
-                        break;
-                    }
-                }
-                if (viableAction != null) {
-                    break;
-                }
-            }
-
-            if (viableAction != null) {
-                Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
-                rebuilder.setActions(viableAction);
-                rebuilder.build(); // will rewrite n
-            }
-        }
-    }
-
-    public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
-        if (!isDeviceProvisioned()) return;
-
-        final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
-        final boolean afterKeyguardGone = intent.isActivity()
-                && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
-                mCurrentUserId);
-        dismissKeyguardThenExecute(new OnDismissAction() {
-            @Override
-            public boolean onDismiss() {
-                new Thread() {
-                    @Override
-                    public void run() {
-                        try {
-                            // The intent we are sending is for the application, which
-                            // won't have permission to immediately start an activity after
-                            // the user switches to home.  We know it is safe to do at this
-                            // point, so make sure new activity switches are now allowed.
-                            ActivityManager.getService().resumeAppSwitches();
-                        } catch (RemoteException e) {
-                        }
-                        try {
-                            intent.send(null, 0, null, null, null, null, getActivityOptions());
-                        } catch (PendingIntent.CanceledException e) {
-                            // the stack trace isn't very helpful here.
-                            // Just log the exception message.
-                            Log.w(TAG, "Sending intent failed: " + e);
-
-                            // TODO: Dismiss Keyguard.
-                        }
-                        if (intent.isActivity()) {
-                            mAssistManager.hideAssist();
-                        }
-                    }
-                }.start();
-
-                // close the shade if it was open
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                        true /* force */, true /* delayed */);
-                visibilityChanged(false);
-
-                return true;
-            }
-        }, afterKeyguardGone);
-    }
-
-    public void addPostCollapseAction(Runnable r) {
-    }
-
-    public boolean isCollapsing() {
-        return false;
-    }
-
-    public void wakeUpIfDozing(long time, PointF where) {
-    }
-
-    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.");
-                return;
-            }
-
-            v.getLocationInWindow(mTmpInt2);
-            wakeUpIfDozing(SystemClock.uptimeMillis(),
-                    new PointF(mTmpInt2[0] + v.getWidth() / 2, mTmpInt2[1] + v.getHeight() / 2));
-
-            final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-            final StatusBarNotification sbn = row.getStatusBarNotification();
-            if (sbn == null) {
-                Log.e(TAG, "NotificationClicker called on an unclickable notification,");
-                return;
-            }
-
-            // Check if the notification is displaying the gear, if so slide notification back
-            if (row.getSettingsRow() != null && row.getSettingsRow().isVisible()) {
-                row.animateTranslateNotification(0);
-                return;
-            }
-
-            Notification notification = sbn.getNotification();
-            final PendingIntent intent = notification.contentIntent != null
-                    ? notification.contentIntent
-                    : notification.fullScreenIntent;
-            final String notificationKey = sbn.getKey();
-
-            // Mark notification for one frame.
-            row.setJustClicked(true);
-            DejankUtils.postAfterTraversal(new Runnable() {
-                @Override
-                public void run() {
-                    row.setJustClicked(false);
-                }
-            });
-
-            final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
-            final boolean afterKeyguardGone = intent.isActivity()
-                    && 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.
-
-                        if (isPanelFullyCollapsed()) {
-                            HeadsUpManager.setIsClickedNotification(row, true);
-                        }
-                        //
-                        // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
-                        // become canceled shortly by NoMan, but we can't assume that.
-                        mHeadsUpManager.releaseImmediately(notificationKey);
-                    }
-                    StatusBarNotification parentToCancel = null;
-                    if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
-                        StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn)
-                                        .getStatusBarNotification();
-                        if (shouldAutoCancel(summarySbn)) {
-                            parentToCancel = summarySbn;
-                        }
-                    }
-                    final StatusBarNotification parentToCancelFinal = parentToCancel;
-                    new Thread() {
-                        @Override
-                        public void run() {
-                            try {
-                                // The intent we are sending is for the application, which
-                                // won't have permission to immediately start an activity after
-                                // the user switches to home.  We know it is safe to do at this
-                                // point, so make sure new activity switches are now allowed.
-                                ActivityManager.getService().resumeAppSwitches();
-                            } catch (RemoteException e) {
-                            }
-                            if (intent != null) {
-                                // If we are launching a work activity and require to launch
-                                // separate work challenge, we defer the activity action and cancel
-                                // notification until work challenge is unlocked.
-                                if (intent.isActivity()) {
-                                    final int userId = intent.getCreatorUserHandle()
-                                            .getIdentifier();
-                                    if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
-                                            && mKeyguardManager.isDeviceLocked(userId)) {
-                                        boolean canBypass = false;
-                                        try {
-                                            canBypass = ActivityManager.getService()
-                                                    .canBypassWorkChallenge(intent);
-                                        } catch (RemoteException e) {
-                                        }
-                                        // For direct-boot aware activities, they can be shown when
-                                        // the device is still locked without triggering the work
-                                        // challenge.
-                                        if ((!canBypass) && startWorkChallengeIfNecessary(userId,
-                                                    intent.getIntentSender(), notificationKey)) {
-                                            // Show work challenge, do not run PendingIntent and
-                                            // remove notification
-                                            return;
-                                        }
-                                    }
-                                }
-                                try {
-                                    intent.send(null, 0, null, null, null, null,
-                                            getActivityOptions());
-                                } catch (PendingIntent.CanceledException e) {
-                                    // the stack trace isn't very helpful here.
-                                    // Just log the exception message.
-                                    Log.w(TAG, "Sending contentIntent failed: " + e);
-
-                                    // TODO: Dismiss Keyguard.
-                                }
-                                if (intent.isActivity()) {
-                                    mAssistManager.hideAssist();
-                                }
-                            }
-
-                            try {
-                                mBarService.onNotificationClick(notificationKey);
-                            } catch (RemoteException ex) {
-                                // system process is dead if we're here.
-                            }
-                            if (parentToCancelFinal != null) {
-                                // We have to post it to the UI thread for synchronization
-                                mHandler.post(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        Runnable removeRunnable = new Runnable() {
-                                            @Override
-                                            public void run() {
-                                                performRemoveNotification(parentToCancelFinal);
-                                            }
-                                        };
-                                        if (isCollapsing()) {
-                                            // To avoid lags we're only performing the remove
-                                            // after the shade was collapsed
-                                            addPostCollapseAction(removeRunnable);
-                                        } else {
-                                            removeRunnable.run();
-                                        }
-                                    }
-                                });
-                            }
-                        }
-                    }.start();
-
-                    // close the shade if it was open
-                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */, true /* delayed */);
-                    visibilityChanged(false);
-
-                    return true;
-                }
-            }, afterKeyguardGone);
-        }
-
-        private boolean shouldAutoCancel(StatusBarNotification sbn) {
-            int flags = sbn.getNotification().flags;
-            if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
-                return false;
-            }
-            if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                return false;
-            }
-            return true;
-        }
-
-        public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
-            Notification notification = sbn.getNotification();
-            if (notification.contentIntent != null || notification.fullScreenIntent != null) {
-                row.setOnClickListener(this);
-            } else {
-                row.setOnClickListener(null);
-            }
-        }
-    }
-
-    public void animateCollapsePanels(int flags, boolean force) {
-    }
-
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
-    }
-
-    protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender,
-            String notificationKey) {
-        final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null,
-                null, userId);
-        if (newIntent == null) {
-            return false;
-        }
-        final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
-        callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender);
-        callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey);
-        callBackIntent.setPackage(mContext.getPackageName());
-
-        PendingIntent callBackPendingIntent = PendingIntent.getBroadcast(
-                mContext,
-                0,
-                callBackIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT |
-                        PendingIntent.FLAG_ONE_SHOT |
-                        PendingIntent.FLAG_IMMUTABLE);
-        newIntent.putExtra(
-                Intent.EXTRA_INTENT,
-                callBackPendingIntent.getIntentSender());
-        try {
-            ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent,
-                    null /*options*/);
-        } catch (RemoteException ex) {
-            // ignore
-        }
-        return true;
-    }
-
-    protected Bundle getActivityOptions() {
-        // Anything launched from the notification shade should always go into the
-        // fullscreen stack.
-        ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID);
-        return options.toBundle();
-    }
-
-    protected void visibilityChanged(boolean visible) {
-        if (mVisible != visible) {
-            mVisible = visible;
-            if (!visible) {
-                dismissPopups();
-            }
-        }
-        updateVisibleToUser();
-    }
-
-    protected void updateVisibleToUser() {
-        boolean oldVisibleToUser = mVisibleToUser;
-        mVisibleToUser = mVisible && mDeviceInteractive;
-
-        if (oldVisibleToUser != mVisibleToUser) {
-            handleVisibleToUserChanged(mVisibleToUser);
-        }
-    }
-
-    /**
-     * The LEDs are turned off when the notification panel is shown, even just a little bit.
-     * See also PhoneStatusBar.setPanelExpanded for another place where we attempt to do this.
-     */
-    protected void handleVisibleToUserChanged(boolean visibleToUser) {
-        try {
-            if (visibleToUser) {
-                boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
-                boolean clearNotificationEffects =
-                        !isPanelFullyCollapsed() &&
-                        (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
-                int notificationLoad = mNotificationData.getActiveNotifications().size();
-                if (pinnedHeadsUp && isPanelFullyCollapsed())  {
-                    notificationLoad = 1;
-                } else {
-                    MetricsLogger.histogram(mContext, "note_load", notificationLoad);
-                }
-                mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
-            } else {
-                mBarService.onPanelHidden();
-            }
-        } catch (RemoteException ex) {
-            // Won't fail unless the world has ended.
-        }
-    }
-
-    /**
-     * Clear Buzz/Beep/Blink.
-     */
-    public void clearNotificationEffects() {
-        try {
-            mBarService.clearNotificationEffects();
-        } catch (RemoteException e) {
-            // Won't fail unless the world has ended.
-        }
-    }
-
-    public abstract boolean isPanelFullyCollapsed();
-
-    /**
-     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
-     * about the failure.
-     *
-     * WARNING: this will call back into us.  Don't hold any locks.
-     */
-    void handleNotificationError(StatusBarNotification n, String message) {
-        removeNotification(n.getKey(), null);
-        try {
-            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
-                    n.getInitialPid(), message, n.getUserId());
-        } catch (RemoteException ex) {
-            // The end is nigh.
-        }
-    }
-
-    protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
-        NotificationData.Entry entry = mNotificationData.remove(key, ranking);
-        if (entry == null) {
-            Log.w(TAG, "removeNotification for unknown key: " + key);
-            return null;
-        }
-        updateNotifications();
-        return entry.notification;
-    }
-
-    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
-        if (DEBUG) {
-            Log.d(TAG, "createNotificationViews(notification=" + sbn);
-        }
-        NotificationData.Entry entry = new NotificationData.Entry(sbn);
-        try {
-            entry.createIcons(mContext, sbn);
-        } catch (NotificationData.IconException exception) {
-            handleNotificationError(sbn, exception.getMessage());
-        }
-
-        // Construct the expanded view.
-        if (!inflateViews(entry, mStackScroller)) {
-            handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
-            return null;
-        }
-        return entry;
-    }
-
-    protected void addNotificationViews(Entry entry, RankingMap ranking) {
-        if (entry == null) {
-            return;
-        }
-        // Add the expanded view and icon.
-        mNotificationData.add(entry, ranking);
-        updateNotifications();
-    }
-
-    /**
-     * @param recompute wheter the number should be recomputed
-     * @return The number of notifications we show on Keyguard.
-     */
-    protected abstract int getMaxKeyguardNotifications(boolean recompute);
-
-    /**
-     * Updates expanded, dimmed and locked states of notification rows.
-     */
-    protected void updateRowStates() {
-        final int N = mStackScroller.getChildCount();
-
-        int visibleNotifications = 0;
-        boolean onKeyguard = mState == StatusBarState.KEYGUARD;
-        int maxNotifications = -1;
-        if (onKeyguard) {
-            maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
-        }
-        mStackScroller.setMaxDisplayedNotifications(maxNotifications);
-        Stack<ExpandableNotificationRow> stack = new Stack<>();
-        for (int i = N - 1; i >= 0; i--) {
-            View child = mStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            stack.push((ExpandableNotificationRow) child);
-        }
-        while(!stack.isEmpty()) {
-            ExpandableNotificationRow row = stack.pop();
-            NotificationData.Entry entry = row.getEntry();
-            boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification);
-            if (onKeyguard) {
-                row.setOnKeyguard(true);
-            } else {
-                row.setOnKeyguard(false);
-                row.setSystemExpanded(visibleNotifications == 0 && !childNotification);
-            }
-            entry.row.setShowAmbient(isDozing());
-            int userId = entry.notification.getUserId();
-            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
-                    entry.notification) && !entry.row.isRemoved();
-            boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
-            if (suppressedSummary
-                    || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications)
-                    || (onKeyguard && !showOnKeyguard)) {
-                entry.row.setVisibility(View.GONE);
-            } else {
-                boolean wasGone = entry.row.getVisibility() == View.GONE;
-                if (wasGone) {
-                    entry.row.setVisibility(View.VISIBLE);
-                }
-                if (!childNotification && !entry.row.isRemoved()) {
-                    if (wasGone) {
-                        // notify the scroller of a child addition
-                        mStackScroller.generateAddAnimation(entry.row,
-                                !showOnKeyguard /* fromMoreCard */);
-                    }
-                    visibleNotifications++;
-                }
-            }
-            if (row.isSummaryWithChildren()) {
-                List<ExpandableNotificationRow> notificationChildren =
-                        row.getNotificationChildren();
-                int size = notificationChildren.size();
-                for (int i = size - 1; i >= 0; i--) {
-                    stack.push(notificationChildren.get(i));
-                }
-            }
-        }
-
-        mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
-        mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
-        mStackScroller.changeViewPosition(mNotificationShelf, mStackScroller.getChildCount() - 3);
-    }
-
-    public boolean isDozing() {
-        return false;
-    }
-
-    public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
-        return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
-    }
-
-    protected void setZenMode(int mode) {
-        if (!isDeviceProvisioned()) return;
-        mZenMode = mode;
-        updateNotifications();
-    }
-
-    // extended in PhoneStatusBar
-    protected void setShowLockscreenNotifications(boolean show) {
-        mShowLockscreenNotifications = show;
-    }
-
-    protected void setLockScreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
-        mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
-    }
-
-    private void updateLockscreenNotificationSetting() {
-        final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
-                1,
-                mCurrentUserId) != 0;
-        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
-                null /* admin */, mCurrentUserId);
-        final boolean allowedByDpm = (dpmFlags
-                & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
-
-        setShowLockscreenNotifications(show && allowedByDpm);
-
-        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
-            final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
-                    0,
-                    mCurrentUserId) != 0;
-            final boolean remoteInputDpm =
-                    (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
-
-            setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm);
-        } else {
-            setLockScreenAllowRemoteInput(false);
-        }
-    }
-
-    protected abstract void setAreThereNotifications();
-    protected abstract void updateNotifications();
-
-    public abstract void addNotification(StatusBarNotification notification,
-            RankingMap ranking, Entry oldEntry);
-    protected abstract void updateNotificationRanking(RankingMap ranking);
-    public abstract void removeNotification(String key, RankingMap ranking);
-
-    public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
-        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
-        final String key = notification.getKey();
-        Entry entry = mNotificationData.get(key);
-        if (entry == null) {
-            return;
-        } else {
-            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
-            mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
-        }
-
-        Notification n = notification.getNotification();
-        mNotificationData.updateRanking(ranking);
-
-        boolean applyInPlace;
-        try {
-            applyInPlace = entry.cacheContentViews(mContext, notification.getNotification(),
-                    mNotificationData.isAmbient(key));
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Unable to get notification remote views", e);
-            applyInPlace = false;
-        }
-        boolean shouldPeek = shouldPeek(entry, notification);
-        boolean alertAgain = alertAgain(entry, n);
-        if (DEBUG) {
-            Log.d(TAG, "applyInPlace=" + applyInPlace
-                    + " shouldPeek=" + shouldPeek
-                    + " alertAgain=" + alertAgain);
-        }
-
-        final StatusBarNotification oldNotification = entry.notification;
-        entry.notification = notification;
-        mGroupManager.onEntryUpdated(entry, oldNotification);
-
-        boolean updateSuccessful = false;
-        try {
-            if (applyInPlace) {
-                if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
-                try {
-                    entry.updateIcons(mContext, n);
-                    updateNotificationViews(entry, notification);
-                    updateSuccessful = true;
-                } catch (RuntimeException e) {
-                    // It failed to apply cleanly.
-                    Log.w(TAG, "Couldn't reapply views for package " +
-                            notification.getPackageName(), e);
-                }
-            }
-            if (!updateSuccessful) {
-                entry.updateIcons(mContext, n);
-                if (!inflateViews(entry, mStackScroller)) {
-                    handleNotificationError(notification, "Couldn't update remote views for: "
-                            + notification);
-                }
-            }
-        } catch (NotificationData.IconException e) {
-            handleNotificationError(notification, e.getMessage());
-        }
-        updateHeadsUp(key, entry, shouldPeek, alertAgain);
-        updateNotifications();
-
-        if (!notification.isClearable()) {
-            // The user may have performed a dismiss action on the notification, since it's
-            // not clearable we should snap it back.
-            mStackScroller.snapViewIfNeeded(entry.row);
-        }
-
-        if (DEBUG) {
-            // Is this for you?
-            boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
-            Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
-        }
-
-        setAreThereNotifications();
-    }
-
-    protected abstract void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
-            boolean alertAgain);
-
-    private void updateNotificationViews(Entry entry, StatusBarNotification sbn) {
-        final RemoteViews contentView = entry.cachedContentView;
-        final RemoteViews bigContentView = entry.cachedBigContentView;
-        final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
-        final RemoteViews publicContentView = entry.cachedPublicContentView;
-
-        // Reapply the RemoteViews
-        contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
-        if (bigContentView != null && entry.getExpandedContentView() != null) {
-            bigContentView.reapply(sbn.getPackageContext(mContext),
-                    entry.getExpandedContentView(),
-                    mOnClickHandler);
-        }
-        View headsUpChild = entry.getHeadsUpContentView();
-        if (headsUpContentView != null && headsUpChild != null) {
-            headsUpContentView.reapply(sbn.getPackageContext(mContext),
-                    headsUpChild, mOnClickHandler);
-        }
-        if (publicContentView != null && entry.getPublicContentView() != null) {
-            publicContentView.reapply(sbn.getPackageContext(mContext),
-                    entry.getPublicContentView(), mOnClickHandler);
-        }
-        // update the contentIntent
-        mNotificationClicker.register(entry.row, sbn);
-
-        entry.row.onNotificationUpdated(entry);
-        entry.row.resetHeight();
-    }
-
-    protected void updatePublicContentView(Entry entry,
-            StatusBarNotification sbn) {
-        final RemoteViews publicContentView = entry.cachedPublicContentView;
-        View inflatedView = entry.getPublicContentView();
-        if (entry.autoRedacted && publicContentView != null && inflatedView != null) {
-            final boolean disabledByPolicy =
-                    !adminAllowsUnredactedNotifications(entry.notification.getUserId());
-            String notificationHiddenText = mContext.getString(disabledByPolicy
-                    ? com.android.internal.R.string.notification_hidden_by_policy_text
-                    : com.android.internal.R.string.notification_hidden_text);
-            TextView titleView = (TextView) inflatedView.findViewById(android.R.id.title);
-            if (titleView != null
-                    && !titleView.getText().toString().equals(notificationHiddenText)) {
-                publicContentView.setTextViewText(android.R.id.title, notificationHiddenText);
-                publicContentView.reapply(sbn.getPackageContext(mContext),
-                        inflatedView, mOnClickHandler);
-                entry.row.onNotificationUpdated(entry);
-            }
-        }
-    }
-
-    protected void notifyHeadsUpScreenOff() {
-        maybeEscalateHeadsUp();
-    }
-
-    private boolean alertAgain(Entry oldEntry, Notification newNotification) {
-        return oldEntry == null || !oldEntry.hasInterrupted()
-                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
-    }
-
-    protected boolean shouldPeek(Entry entry) {
-        return shouldPeek(entry, entry.notification);
-    }
-
-    protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
-        if (!mUseHeadsUp || isDeviceInVrMode()) {
-            return false;
-        }
-
-        if (mNotificationData.shouldFilterOut(sbn)) {
-            if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
-            return false;
-        }
-
-        boolean inUse = mPowerManager.isScreenOn();
-        try {
-            inUse = inUse && !mDreamManager.isDreaming();
-        } catch (RemoteException e) {
-            Log.d(TAG, "failed to query dream manager", e);
-        }
-
-        if (!inUse && !isDozing()) {
-            if (DEBUG) {
-                Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
-            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
-            return false;
-        }
-
-        if (entry.hasJustLaunchedFullScreenIntent()) {
-            if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
-            return false;
-        }
-
-        if (isSnoozedPackage(sbn)) {
-            if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
-            return false;
-        }
-
-        if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) {
-            if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
-            return false;
-        }
-
-        if (sbn.getNotification().fullScreenIntent != null) {
-            if (mAccessibilityManager.isTouchExplorationEnabled()) {
-                if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
-                return false;
-            } else {
-                // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
-                return !mStatusBarKeyguardViewManager.isShowing()
-                        || mStatusBarKeyguardViewManager.isOccluded();
-            }
-        }
-
-        return true;
-    }
-
-    protected abstract boolean isSnoozedPackage(StatusBarNotification sbn);
-
-    public void setInteracting(int barWindow, boolean interacting) {
-        // hook for subclasses
-    }
-
-    public void setBouncerShowing(boolean bouncerShowing) {
-        mBouncerShowing = bouncerShowing;
-    }
-
-    /**
-     * @return Whether the security bouncer from Keyguard is showing.
-     */
-    public boolean isBouncerShowing() {
-        return mBouncerShowing;
-    }
-
-    public void destroy() {
-        mContext.unregisterReceiver(mBroadcastReceiver);
-        try {
-            mNotificationListener.unregisterAsSystemService();
-        } catch (RemoteException e) {
-            // Ignore.
-        }
-        mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
-    }
-
-    /**
-     * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then
-     *         return PackageManager for mContext
-     */
-    public static PackageManager getPackageManagerForUser(Context context, int userId) {
-        Context contextForUser = context;
-        // UserHandle defines special userId as negative values, e.g. USER_ALL
-        if (userId >= 0) {
-            try {
-                // Create a context for the correct user so if a package isn't installed
-                // for user 0 we can still load information about the package.
-                contextForUser =
-                        context.createPackageContextAsUser(context.getPackageName(),
-                        Context.CONTEXT_RESTRICTED,
-                        new UserHandle(userId));
-            } catch (NameNotFoundException e) {
-                // Shouldn't fail to find the package name for system ui.
-            }
-        }
-        return contextForUser.getPackageManager();
-    }
-
-    @Override
-    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
-        try {
-            mBarService.onNotificationExpansionChanged(key, userAction, expanded);
-        } catch (RemoteException e) {
-            // Ignore.
-        }
-    }
-
-    public boolean isKeyguardSecure() {
-        if (mStatusBarKeyguardViewManager == null) {
-            // startKeyguard() hasn't been called yet, so we don't know.
-            // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this
-            // value onVisibilityChanged().
-            Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false",
-                    new Throwable());
-            return false;
-        }
-        return mStatusBarKeyguardViewManager.isSecure();
-    }
-
-    @Override
-    public void showAssistDisclosure() {
-        if (mAssistManager != null) {
-            mAssistManager.showDisclosure();
-        }
-    }
-
-    @Override
-    public void startAssist(Bundle args) {
-        if (mAssistManager != null) {
-            mAssistManager.startAssist(args);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 9177f4b..d1245b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -56,6 +56,7 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.stack.AnimationProperties;
 import com.android.systemui.statusbar.stack.ExpandableViewState;
@@ -436,7 +437,7 @@
      * @param parent the new parent notification
      */
     public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {;
-        boolean childInGroup = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
+        boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
         mNotificationParent = childInGroup ? parent : null;
         mPrivateLayout.setIsChildInGroup(childInGroup);
         resetBackgroundAlpha();
@@ -1420,7 +1421,7 @@
     }
 
     private void onChildrenCountChanged() {
-        mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+        mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS
                 && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
         if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
             mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index a1299e8..e0ddf13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -89,6 +89,7 @@
     private StatusBarNotification mStatusBarNotification;
     private NotificationGroupManager mGroupManager;
     private RemoteInputController mRemoteInputController;
+    private Runnable mExpandedVisibleListener;
 
     private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
             = new ViewTreeObserver.OnPreDrawListener() {
@@ -498,11 +499,6 @@
                         com.android.internal.R.dimen.notification_action_list_height);
         }
 
-        if (isVisibleOrTransitioning(VISIBLE_TYPE_AMBIENT)) {
-            return mContractedChild.getHeight() + mContext.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.notification_action_list_height);
-        }
-
         // Transition between heads-up & expanded, or pinned.
         if (mHeadsUpChild != null && mExpandedChild != null) {
             boolean transitioningBetweenHunAndExpanded =
@@ -522,7 +518,9 @@
         }
 
         int hint;
-        if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
+        if (mAmbientChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_AMBIENT)) {
+            hint = mAmbientChild.getHeight();
+        } else if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
             hint = mHeadsUpChild.getHeight();
         } else if (mExpandedChild != null) {
             hint = mExpandedChild.getHeight();
@@ -684,10 +682,6 @@
                     visibleView.setVisibility(VISIBLE);
                     transferRemoteInputFocus(visibleType);
                 }
-                NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
-                if (visibleWrapper != null) {
-                    visibleWrapper.setContentHeight(mContentHeight, getMinContentHeightHint());
-                }
 
                 if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
                         || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
@@ -701,6 +695,10 @@
                 if (changedType) {
                     focusExpandButtonIfNecessary();
                 }
+                NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
+                if (visibleWrapper != null) {
+                    visibleWrapper.setContentHeight(mContentHeight, getMinContentHeightHint());
+                }
                 updateBackgroundColor(animate);
             }
         }
@@ -712,11 +710,21 @@
         forceUpdateVisibility(VISIBLE_TYPE_HEADSUP, mHeadsUpChild, mHeadsUpWrapper);
         forceUpdateVisibility(VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView);
         forceUpdateVisibility(VISIBLE_TYPE_AMBIENT, mAmbientChild, mAmbientWrapper);
+        fireExpandedVisibleListenerIfVisible();
         // forceUpdateVisibilities cancels outstanding animations without updating the
         // mAnimationStartVisibleType. Do so here instead.
         mAnimationStartVisibleType = UNDEFINED;
     }
 
+    private void fireExpandedVisibleListenerIfVisible() {
+        if (mExpandedVisibleListener != null && mExpandedChild != null && isShown()
+                && mExpandedChild.getVisibility() == VISIBLE) {
+            Runnable listener = mExpandedVisibleListener;
+            mExpandedVisibleListener = null;
+            listener.run();
+        }
+    }
+
     private void forceUpdateVisibility(int type, View view, TransformableView wrapper) {
         if (view == null) {
             return;
@@ -770,6 +778,7 @@
                 mSingleLineView, mSingleLineView);
         updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT,
                 mAmbientChild, mAmbientWrapper);
+        fireExpandedVisibleListenerIfVisible();
         // updateViewVisibilities cancels outstanding animations without updating the
         // mAnimationStartVisibleType. Do so here instead.
         mAnimationStartVisibleType = UNDEFINED;
@@ -801,6 +810,7 @@
                 mAnimationStartVisibleType = UNDEFINED;
             }
         });
+        fireExpandedVisibleListenerIfVisible();
     }
 
     private void transferRemoteInputFocus(int visibleType) {
@@ -1304,6 +1314,24 @@
         }
     }
 
+    @Override
+    public void onVisibilityAggregated(boolean isVisible) {
+        super.onVisibilityAggregated(isVisible);
+        if (isVisible) {
+            fireExpandedVisibleListenerIfVisible();
+        }
+    }
+
+    /**
+     * Sets a one-shot listener for when the expanded view becomes visible.
+     *
+     * This will fire the listener immediately if the expanded view is already visible.
+     */
+    public void setOnExpandedVisibleListener(Runnable r) {
+        mExpandedVisibleListener = r;
+        fireExpandedVisibleListenerIfVisible();
+    }
+
     public void setIsLowPriority(boolean isLowPriority) {
         mIsLowPriority = isLowPriority;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 8c04a1a..bbfcf31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -25,6 +25,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.view.View;
@@ -34,12 +35,14 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -71,6 +74,7 @@
         public RemoteViews cachedPublicContentView;
         public RemoteViews cachedAmbientContentView;
         public CharSequence remoteInputText;
+        public List<SnoozeCriterion> snoozeCriteria;
         private int mCachedContrastColor = COLOR_INVALID;
         private int mCachedContrastColorIsFor = COLOR_INVALID;
 
@@ -456,6 +460,14 @@
          return null;
     }
 
+    public List<SnoozeCriterion> getSnoozeCriteria(String key) {
+        if (mRankingMap != null) {
+            mRankingMap.getRanking(key, mTmpRanking);
+            return mTmpRanking.getSnoozeCriteria();
+        }
+        return null;
+    }
+
     public NotificationChannel getChannel(String key) {
         if (mRankingMap != null) {
             mRankingMap.getRanking(key, mTmpRanking);
@@ -478,6 +490,7 @@
                         mGroupManager.onEntryUpdated(entry, oldSbn);
                     }
                     entry.channel = getChannel(entry.key);
+                    entry.snoozeCriteria = getSnoozeCriteria(entry.key);
                 }
             }
         }
@@ -506,7 +519,7 @@
         Collections.sort(mSortedAndFiltered, mRankingComparator);
     }
 
-    boolean shouldFilterOut(StatusBarNotification sbn) {
+    public boolean shouldFilterOut(StatusBarNotification sbn) {
         if (!(mEnvironment.isDeviceProvisioned() ||
                 showNotificationEvenIfUnprovisioned(sbn))) {
             return true;
@@ -523,7 +536,7 @@
             return true;
         }
 
-        if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+        if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
                 && mGroupManager.isChildInGroupWithSummary(sbn)) {
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 0989846..bdbc9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -91,11 +91,12 @@
         super(context, attrs);
     }
 
-    interface OnSettingsClickListener {
+    public interface OnSettingsClickListener {
         void onClick(View v, int appUid);
     }
 
-    void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
+    public void bindNotification(final PackageManager pm,
+            final INotificationManager iNotificationManager,
             final StatusBarNotification sbn, final NotificationChannel channel,
             OnSettingsClickListener onSettingsClick,
             OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index 670d73e..1992b6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -17,17 +17,21 @@
 
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.List;
 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 com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Color;
+import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
 import android.view.View;
@@ -43,21 +47,17 @@
 public class NotificationSnooze extends LinearLayout
         implements NotificationMenuRowProvider.SnoozeGutsContent, View.OnClickListener {
 
+    private static final int MAX_ASSISTANT_SUGGESTIONS = 2;
     private GutsInteractionListener mGutsInteractionListener;
     private SnoozeListener mSnoozeListener;
     private StatusBarNotification mSbn;
 
-    private TextView mSelectedOption;
-    private TextView mUndo;
+    private TextView mSelectedOptionText;
+    private TextView mUndoButton;
     private ViewGroup mSnoozeOptionView;
+    private List<SnoozeOption> mSnoozeOptions;
 
-    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
-    };
+    private SnoozeOption mSelectedOption;
 
     public NotificationSnooze(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -67,60 +67,88 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         // Create the different options based on list
+        mSnoozeOptions = getDefaultSnoozeOptions();
         createOptionViews();
 
         // Snackbar
-        mSelectedOption = (TextView) findViewById(R.id.snooze_option_default);
-        mSelectedOption.setOnClickListener(this);
-        mUndo = (TextView) findViewById(R.id.undo);
-        mUndo.setOnClickListener(this);
+        mSelectedOptionText = (TextView) findViewById(R.id.snooze_option_default);
+        mSelectedOptionText.setOnClickListener(this);
+        mUndoButton = (TextView) findViewById(R.id.undo);
+        mUndoButton.setOnClickListener(this);
 
         // Default to first option in list
-        setTimeToSnooze(SNOOZE_OPTIONS[0]);
+        setSelected(mSnoozeOptions.get(0));
+    }
+
+    public void setSnoozeOptions(final List<SnoozeCriterion> snoozeList) {
+        if (snoozeList == null) {
+            return;
+        }
+        mSnoozeOptions.clear();
+        mSnoozeOptions = getDefaultSnoozeOptions();
+        final int count = Math.min(MAX_ASSISTANT_SUGGESTIONS, snoozeList.size());
+        for (int i = 0; i < count; i++) {
+            SnoozeCriterion sc = snoozeList.get(i);
+            mSnoozeOptions.add(new SnoozeOption(sc, 0, sc.getExplanation(), sc.getConfirmation()));
+        }
+        createOptionViews();
+    }
+
+    private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+        ArrayList<SnoozeOption> options = new ArrayList<>();
+        options.add(createOption(R.string.snooze_option_15_min, 15));
+        options.add(createOption(R.string.snooze_option_30_min, 30));
+        options.add(createOption(R.string.snooze_option_1_hour, 60));
+        return options;
+    }
+
+    private SnoozeOption createOption(int descriptionResId, int minutes) {
+        Resources res = getResources();
+        String resultText = String.format(
+                res.getString(R.string.snoozed_for_time), res.getString(descriptionResId));
+        return new SnoozeOption(null, minutes, res.getString(descriptionResId), resultText);
     }
 
     private void createOptionViews() {
         mSnoozeOptionView = (ViewGroup) findViewById(R.id.snooze_options);
+        mSnoozeOptionView.removeAllViews();
         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++) {
+
+        // Add all the options
+        for (int i = 0; i < mSnoozeOptions.size(); i++) {
+            SnoozeOption option = mSnoozeOptions.get(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.setText(option.description);
+            tv.setTag(option);
             tv.setOnClickListener(this);
         }
+
+        // Add the undo option as final item
+        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(R.string.snooze_option_dont_snooze);
+        tv.setOnClickListener(this);
     }
 
     private void showSnoozeOptions(boolean show) {
-        mSelectedOption.setVisibility(show ? View.GONE : View.VISIBLE);
-        mUndo.setVisibility(show ? View.GONE : View.VISIBLE);
+        mSelectedOptionText.setVisibility(show ? View.GONE : View.VISIBLE);
+        mUndoButton.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);
+    private void setSelected(SnoozeOption option) {
+        mSelectedOption = option;
+        mSelectedOptionText.setText(option.confirmation);
         showSnoozeOptions(false);
     }
 
@@ -130,19 +158,22 @@
             mGutsInteractionListener.onInteraction(this);
         }
         final int id = v.getId();
-        final Integer tag = (Integer) v.getTag();
+        final SnoozeOption tag = (SnoozeOption) v.getTag();
         if (tag != null) {
-            // From the option list
-            setTimeToSnooze(tag);
+            setSelected(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);
+        } else {
+            undoSnooze();
         }
     }
 
+    private void undoSnooze() {
+        mSelectedOption = null;
+        mGutsInteractionListener.closeGuts(this);
+    }
+
     @Override
     public View getContentView() {
         return this;
@@ -167,9 +198,13 @@
     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);
+        if (mSnoozeListener != null && mSelectedOption != null) {
+            mSnoozeListener.snoozeNotification(mSbn, mSelectedOption);
             return true;
+        } else {
+            // Reset the view once it's closed
+            setSelected(mSnoozeOptions.get(0));
+            showSnoozeOptions(false);
         }
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index f24e40b..f6a5687 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -39,7 +39,7 @@
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -47,7 +47,7 @@
 /**
  * A status bar (and navigation bar) tailored for the automotive use case.
  */
-public class CarStatusBar extends PhoneStatusBar implements
+public class CarStatusBar extends StatusBar implements
         CarBatteryController.BatteryViewHandler {
     private static final String TAG = "CarStatusBar";
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
index c308930..67f8426 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
@@ -20,7 +20,7 @@
 import com.android.systemui.statusbar.ScalingDrawableWrapper;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
-import static com.android.systemui.statusbar.phone.PhoneStatusBar.DEBUG;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
 
 /**
  * Controller that monitors signal strength for a device that is connected via bluetooth.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 8e6c153..f8b6dee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -20,7 +20,7 @@
 import android.view.ViewStub;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 /**
@@ -32,7 +32,7 @@
     private UserGridView mUserGridView;
     private UserSwitcherController mUserSwitcherController;
 
-    public FullscreenUserSwitcher(PhoneStatusBar statusBar,
+    public FullscreenUserSwitcher(StatusBar statusBar,
             UserSwitcherController userSwitcherController,
             ViewStub containerStub) {
         mUserSwitcherController = userSwitcherController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
index d90a21d..137b5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
@@ -29,12 +29,12 @@
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.UserUtil;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 public class UserGridView extends GridView {
 
-    private PhoneStatusBar mStatusBar;
+    private StatusBar mStatusBar;
     private UserSwitcherController mUserSwitcherController;
     private Adapter mAdapter;
     private int mPendingUserId = UserHandle.USER_NULL;
@@ -43,7 +43,7 @@
         super(context, attrs);
     }
 
-    public void init(PhoneStatusBar statusBar, UserSwitcherController userSwitcherController) {
+    public void init(StatusBar statusBar, UserSwitcherController userSwitcherController) {
         mStatusBar = statusBar;
         mUserSwitcherController = userSwitcherController;
         mAdapter = new Adapter(mUserSwitcherController);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 42d9433..0773108 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -16,11 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Build;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -98,7 +94,7 @@
     private DozeScrimController mDozeScrimController;
     private KeyguardViewMediator mKeyguardViewMediator;
     private ScrimController mScrimController;
-    private PhoneStatusBar mPhoneStatusBar;
+    private StatusBar mStatusBar;
     private final UnlockMethodCache mUnlockMethodCache;
     private final Context mContext;
     private boolean mGoingToSleep;
@@ -109,7 +105,7 @@
             DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator,
             ScrimController scrimController,
-            PhoneStatusBar phoneStatusBar,
+            StatusBar statusBar,
             UnlockMethodCache unlockMethodCache) {
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
@@ -119,7 +115,7 @@
         mDozeScrimController = dozeScrimController;
         mKeyguardViewMediator = keyguardViewMediator;
         mScrimController = scrimController;
-        mPhoneStatusBar = phoneStatusBar;
+        mStatusBar = statusBar;
         mUnlockMethodCache = unlockMethodCache;
     }
 
@@ -218,7 +214,7 @@
                 break;
             case MODE_WAKE_AND_UNLOCK_PULSING:
                 Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
-                mPhoneStatusBar.updateMediaMetaData(false /* metaDataChanged */, 
+                mStatusBar.updateMediaMetaData(false /* metaDataChanged */,
                         true /* allowEnterAnimation */);
                 // Fall through.
                 Trace.endSection();
@@ -228,8 +224,8 @@
                 mDozeScrimController.abortPulsing();
                 mKeyguardViewMediator.onWakeAndUnlocking();
                 mScrimController.setWakeAndUnlocking();
-                if (mPhoneStatusBar.getNavigationBarView() != null) {
-                    mPhoneStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
+                if (mStatusBar.getNavigationBarView() != null) {
+                    mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
                 }
                 Trace.endSection();
                 break;
@@ -240,7 +236,7 @@
         if (mMode != MODE_WAKE_AND_UNLOCK_PULSING) {
             mStatusBarWindowManager.setForceDozeBrightness(false);
         }
-        mPhoneStatusBar.notifyFpAuthModeChanged();
+        mStatusBar.notifyFpAuthModeChanged();
         Trace.endSection();
     }
 
@@ -309,7 +305,7 @@
         mMode = MODE_NONE;
         releaseFingerprintWakeLock();
         mStatusBarWindowManager.setForceDozeBrightness(false);
-        mPhoneStatusBar.notifyFpAuthModeChanged();
+        mStatusBar.notifyFpAuthModeChanged();
     }
 
     public void startKeyguardFadingAway() {
@@ -320,14 +316,14 @@
             public void run() {
                 mStatusBarWindowManager.setForceDozeBrightness(false);
             }
-        }, PhoneStatusBar.FADE_KEYGUARD_DURATION_PULSING);
+        }, StatusBar.FADE_KEYGUARD_DURATION_PULSING);
     }
 
     public void finishKeyguardFadingAway() {
         mMode = MODE_NONE;
-        if (mPhoneStatusBar.getNavigationBarView() != null) {
-            mPhoneStatusBar.getNavigationBarView().setWakeAndUnlocking(false);
+        if (mStatusBar.getNavigationBarView() != null) {
+            mStatusBar.getNavigationBarView().setWakeAndUnlocking(false);
         }
-        mPhoneStatusBar.notifyFpAuthModeChanged();
+        mStatusBar.notifyFpAuthModeChanged();
     }
 }
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 7dcf811..bfc0a80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -18,9 +18,8 @@
 
 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;
 
@@ -36,9 +35,7 @@
 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;
@@ -95,7 +92,7 @@
         AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener,
         Tunable {
 
-    final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
+    final static String TAG = "StatusBar/KeyguardBottomAreaView";
 
     public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
     public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
@@ -136,7 +133,7 @@
     private PreviewInflater mPreviewInflater;
     private KeyguardIndicationController mIndicationController;
     private AccessibilityController mAccessibilityController;
-    private PhoneStatusBar mPhoneStatusBar;
+    private StatusBar mStatusBar;
     private KeyguardAffordanceHelper mAffordanceHelper;
 
     private boolean mUserSetupComplete;
@@ -208,7 +205,7 @@
         public boolean performAccessibilityAction(View host, int action, Bundle args) {
             if (action == ACTION_CLICK) {
                 if (host == mLockIcon) {
-                    mPhoneStatusBar.animateCollapsePanels(
+                    mStatusBar.animateCollapsePanels(
                             CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
                     return true;
                 } else if (host == mRightAffordanceView) {
@@ -259,6 +256,7 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
+        mAccessibilityController.addStateChangedCallback(this);
         PluginManager.getInstance(getContext()).addPluginListener(RIGHT_BUTTON_PLUGIN,
                 mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */);
         PluginManager.getInstance(getContext()).addPluginListener(LEFT_BUTTON_PLUGIN,
@@ -270,6 +268,7 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
+        mAccessibilityController.removeStateChangedCallback(this);
         PluginManager.getInstance(getContext()).removePluginListener(mRightListener);
         PluginManager.getInstance(getContext()).removePluginListener(mLeftListener);
         TunerService.get(getContext()).removeTunable(this);
@@ -326,8 +325,8 @@
         mRightAffordanceView.setContentDescription(state.contentDescription);
     }
 
-    public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
-        mPhoneStatusBar = phoneStatusBar;
+    public void setStatusBar(StatusBar statusBar) {
+        mStatusBar = statusBar;
         updateCameraVisibility(); // in case onFinishInflate() was called too early
     }
 
@@ -391,13 +390,13 @@
     private boolean isCameraDisabledByDpm() {
         final DevicePolicyManager dpm =
                 (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
-        if (dpm != null && mPhoneStatusBar != null) {
+        if (dpm != null && mStatusBar != null) {
             try {
                 final int userId = ActivityManager.getService().getCurrentUser().id;
                 final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
                 final  boolean disabledBecauseKeyguardSecure =
                         (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
-                                && mPhoneStatusBar.isKeyguardSecure();
+                                && mStatusBar.isKeyguardSecure();
                 return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
             } catch (RemoteException e) {
                 Log.e(TAG, "Can't get userId", e);
@@ -433,7 +432,7 @@
             if (!mAccessibilityController.isAccessibilityEnabled()) {
                 handleTrustCircleClick();
             } else {
-                mPhoneStatusBar.animateCollapsePanels(
+                mStatusBar.animateCollapsePanels(
                         CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
             }
         }
@@ -570,12 +569,12 @@
                 mAssistManager.launchVoiceAssistFromKeyguard();
             }
         };
-        if (mPhoneStatusBar.isKeyguardCurrentlySecure()) {
+        if (mStatusBar.isKeyguardCurrentlySecure()) {
             AsyncTask.execute(runnable);
         } else {
             boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
                     && TunerService.get(getContext()).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
-            mPhoneStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
+            mStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
                     dismissShade, false /* afterKeyguardGone */, true /* deferred */);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index af6e259..99b3aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -51,7 +51,7 @@
 
     private static final String TAG = "LockscreenWallpaper";
 
-    private final PhoneStatusBar mBar;
+    private final StatusBar mBar;
     private final WallpaperManager mWallpaperManager;
     private final Handler mH;
     private final KeyguardUpdateMonitor mUpdateMonitor;
@@ -64,7 +64,7 @@
     private UserHandle mSelectedUser;
     private AsyncTask<Void, Void, LoaderResult> mLoader;
 
-    public LockscreenWallpaper(Context ctx, PhoneStatusBar bar, Handler h) {
+    public LockscreenWallpaper(Context ctx, StatusBar bar, Handler h) {
         mBar = bar;
         mH = h;
         mWallpaperManager = (WallpaperManager) ctx.getSystemService(Context.WALLPAPER_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index d40326a..62b536e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -20,8 +20,8 @@
 import static android.app.StatusBarManager.windowStateToString;
 
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.PhoneStatusBar.DEBUG_WINDOW_STATE;
-import static com.android.systemui.statusbar.phone.PhoneStatusBar.dumpBarTransitions;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
+import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -67,7 +67,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.SystemUIApplication;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
@@ -105,7 +104,7 @@
     protected AccessibilityManager mAccessibilityManager;
 
     private int mDisabledFlags1;
-    private PhoneStatusBar mPhoneStatusBar;
+    private StatusBar mStatusBar;
     private Recents mRecents;
     private Divider mDivider;
     private WindowManager mWindowManager;
@@ -128,7 +127,7 @@
         super.onCreate(savedInstanceState);
         mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class);
         mCommandQueue.addCallbacks(this);
-        mPhoneStatusBar = SysUiServiceProvider.getComponent(getContext(), PhoneStatusBar.class);
+        mStatusBar = SysUiServiceProvider.getComponent(getContext(), StatusBar.class);
         mRecents = SysUiServiceProvider.getComponent(getContext(), Recents.class);
         mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
         mWindowManager = getContext().getSystemService(WindowManager.class);
@@ -262,7 +261,7 @@
         if (mNavigationBarView != null) {
             mNavigationBarView.setNavigationIconHints(hints);
         }
-        mPhoneStatusBar.checkBarModes();
+        mStatusBar.checkBarModes();
     }
 
     @Override
@@ -300,21 +299,21 @@
 
     /**
      * Calls appTransitionStarting for the nav bar regardless of whether keyguard is going away.
-     * public so PhoneStatusBar can force this when needed.
+     * public so StatusBar can force this when needed.
      */
     public void doAppTransitionStarting(long startTime, long duration) {
         mNavigationBarView.getLightTransitionsController().appTransitionStarting(startTime,
                 duration);
     }
 
-    // Injected from PhoneStatusBar at creation.
+    // Injected from StatusBar at creation.
     public void setCurrentSysuiVisibility(int systemUiVisibility) {
         mSystemUiVisibility = systemUiVisibility;
-        mNavigationBarMode = mPhoneStatusBar.computeBarMode(0, mSystemUiVisibility,
+        mNavigationBarMode = mStatusBar.computeBarMode(0, mSystemUiVisibility,
                 View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
                 View.NAVIGATION_BAR_TRANSPARENT);
         checkNavBarModes();
-        mPhoneStatusBar.touchAutoHide();
+        mStatusBar.touchAutoHide();
         mLightBarController.onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */,
                 true /* nbModeChanged */, mNavigationBarMode);
     }
@@ -331,7 +330,7 @@
 
             // update navigation bar mode
             final int nbMode = getView() == null
-                    ? -1 : mPhoneStatusBar.computeBarMode(oldVal, newVal,
+                    ? -1 : mStatusBar.computeBarMode(oldVal, newVal,
                     View.NAVIGATION_BAR_TRANSIENT, View.NAVIGATION_BAR_TRANSLUCENT,
                     View.NAVIGATION_BAR_TRANSPARENT);
             nbModeChanged = nbMode != -1;
@@ -340,7 +339,7 @@
                     mNavigationBarMode = nbMode;
                     checkNavBarModes();
                 }
-                mPhoneStatusBar.touchAutoHide();
+                mStatusBar.touchAutoHide();
             }
         }
 
@@ -370,7 +369,7 @@
     }
 
     private boolean shouldDisableNavbarGestures() {
-        return !mPhoneStatusBar.isDeviceProvisioned()
+        return !mStatusBar.isDeviceProvisioned()
                 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
     }
 
@@ -418,7 +417,7 @@
                 TelecomManager telecomManager =
                         getContext().getSystemService(TelecomManager.class);
                 if (telecomManager != null && telecomManager.isRinging()) {
-                    if (mPhoneStatusBar.isKeyguardShowing()) {
+                    if (mStatusBar.isKeyguardShowing()) {
                         Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
                                 "No heads up");
                         mHomeBlockedThisTouch = true;
@@ -428,18 +427,18 @@
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mPhoneStatusBar.awakenDreams();
+                mStatusBar.awakenDreams();
                 break;
         }
         return false;
     }
 
     private void onVerticalChanged(boolean isVertical) {
-        mPhoneStatusBar.setQsScrimEnabled(!isVertical);
+        mStatusBar.setQsScrimEnabled(!isVertical);
     }
 
     private boolean onNavigationTouch(View v, MotionEvent event) {
-        mPhoneStatusBar.checkUserAutohide(v, event);
+        mStatusBar.checkUserAutohide(v, event);
         return false;
     }
 
@@ -450,7 +449,7 @@
         }
         MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS);
         mAssistManager.startAssist(new Bundle() /* args */);
-        mPhoneStatusBar.awakenDreams();
+        mStatusBar.awakenDreams();
         if (mNavigationBarView != null) {
             mNavigationBarView.abortCurrentGesture();
         }
@@ -478,7 +477,7 @@
             LatencyTracker.getInstance(getContext()).onActionStart(
                     LatencyTracker.ACTION_TOGGLE_RECENTS);
         }
-        mPhoneStatusBar.awakenDreams();
+        mStatusBar.awakenDreams();
         mCommandQueue.toggleRecentApps();
     }
 
@@ -550,11 +549,11 @@
             return false;
         }
 
-        return mPhoneStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
+        return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
                 MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
     }
 
-    // ----- Methods that PhoneStatusBar talks to (should be minimized) -----
+    // ----- Methods that StatusBar talks to (should be minimized) -----
 
     public void setLightBarController(LightBarController lightBarController) {
         mLightBarController = lightBarController;
@@ -584,7 +583,7 @@
     }
 
     public void checkNavBarModes() {
-        mPhoneStatusBar.checkBarMode(mNavigationBarMode,
+        mStatusBar.checkBarMode(mNavigationBarMode,
                 mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 319f124..5e988fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -28,7 +28,6 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
@@ -53,7 +52,6 @@
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.phone.NavGesture;
 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
-import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.policy.DeadZone;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
@@ -553,8 +551,8 @@
                 }
 
                 @Override
-                public void onDockedStackMinimizedChanged(boolean minimized, long animDuration)
-                        throws RemoteException {
+                public void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
+                        boolean isHomeStackResizable) throws RemoteException {
                 }
 
                 @Override
@@ -784,7 +782,7 @@
         final Point size = new Point();
         mDisplay.getRealSize(size);
 
-        pw.println(String.format("      this: " + PhoneStatusBar.viewInfo(this)
+        pw.println(String.format("      this: " + StatusBar.viewInfo(this)
                         + " " + visibilityToString(getVisibility())));
 
         getWindowVisibleDisplayFrame(r);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 32b9969..0386398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -33,7 +33,7 @@
     private int mIconHPadding;
     private int mIconTint = Color.WHITE;
 
-    private PhoneStatusBar mPhoneStatusBar;
+    private StatusBar mStatusBar;
     protected View mNotificationIconArea;
     private NotificationIconContainer mNotificationIcons;
     private NotificationIconContainer mShelfIcons;
@@ -41,8 +41,8 @@
     private NotificationStackScrollLayout mNotificationScrollLayout;
     private Context mContext;
 
-    public NotificationIconAreaController(Context context, PhoneStatusBar phoneStatusBar) {
-        mPhoneStatusBar = phoneStatusBar;
+    public NotificationIconAreaController(Context context, StatusBar statusBar) {
+        mStatusBar = statusBar;
         mNotificationColorUtil = NotificationColorUtil.getInstance(context);
         mContext = context;
 
@@ -64,11 +64,11 @@
         mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
                 R.id.notificationIcons);
 
-        NotificationShelf shelf = mPhoneStatusBar.getNotificationShelf();
+        NotificationShelf shelf = mStatusBar.getNotificationShelf();
         mShelfIcons = shelf.getShelfIcons();
         shelf.setCollapsedIcons(mNotificationIcons);
 
-        mNotificationScrollLayout = mPhoneStatusBar.getNotificationScrollLayout();
+        mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
     }
 
     public void onDensityOrFontScaleChanged(Context context) {
@@ -124,7 +124,7 @@
     }
 
     protected int getHeight() {
-        return mPhoneStatusBar.getStatusBarHeight();
+        return mStatusBar.getStatusBarHeight();
     }
 
     protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
@@ -133,7 +133,7 @@
                 && !NotificationData.showNotificationEvenIfUnprovisioned(entry.notification)) {
             return false;
         }
-        if (!PhoneStatusBar.isTopLevelChild(entry)) {
+        if (!StatusBar.isTopLevelChild(entry)) {
             return false;
         }
         if (entry.row.getVisibility() == View.GONE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 3bdd5e5..fe7e915 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -92,7 +92,7 @@
 
     public static final long DOZE_ANIMATION_DURATION = 700;
 
-    private KeyguardAffordanceHelper mAfforanceHelper;
+    private KeyguardAffordanceHelper mAffordanceHelper;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private KeyguardStatusBarView mKeyguardStatusBar;
     private QS mQs;
@@ -219,7 +219,7 @@
         mFalsingManager = FalsingManager.getInstance(context);
     }
 
-    public void setStatusBar(PhoneStatusBar bar) {
+    public void setStatusBar(StatusBar bar) {
         mStatusBar = bar;
     }
 
@@ -239,8 +239,8 @@
         mNotificationStackScroller.setOnEmptySpaceClickListener(this);
         mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
         mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
-        mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
-        mKeyguardBottomArea.setAffordanceHelper(mAfforanceHelper);
+        mAffordanceHelper = new KeyguardAffordanceHelper(this, getContext());
+        mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
         mLastOrientation = getResources().getConfiguration().orientation;
 
         mQsFrame = (FrameLayout) findViewById(R.id.qs_frame);
@@ -516,7 +516,7 @@
         mBlockTouches = false;
         mUnlockIconActive = false;
         if (!mLaunchingAffordance) {
-            mAfforanceHelper.reset(false);
+            mAffordanceHelper.reset(false);
             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
         }
         closeQs();
@@ -762,8 +762,9 @@
         }
         if ((!mIsExpanding || mHintAnimationRunning)
                 && !mQsExpanded
-                && mStatusBar.getBarState() != StatusBarState.SHADE) {
-            mAfforanceHelper.onTouchEvent(event);
+                && mStatusBar.getBarState() != StatusBarState.SHADE
+                && !mDozing) {
+            mAffordanceHelper.onTouchEvent(event);
         }
         if (mOnlyAffordanceInThisMotion) {
             return true;
@@ -881,7 +882,7 @@
 
     @Override
     protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
-        return !mAfforanceHelper.isOnAffordanceIcon(x, y);
+        return !mAffordanceHelper.isOnAffordanceIcon(x, y);
     }
 
     private void onQsTouch(MotionEvent event) {
@@ -1722,7 +1723,7 @@
         }
         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
-            mAfforanceHelper.animateHideLeftRightIcon();
+            mAffordanceHelper.animateHideLeftRightIcon();
         }
         mNotificationStackScroller.onPanelTrackingStarted();
     }
@@ -1739,7 +1740,7 @@
         if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
             if (!mHintAnimationRunning) {
-                mAfforanceHelper.reset(true);
+                mAffordanceHelper.reset(true);
             }
         }
         if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
@@ -1785,7 +1786,7 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        mAfforanceHelper.onConfigurationChanged();
+        mAffordanceHelper.onConfigurationChanged();
         if (newConfig.orientation != mLastOrientation) {
             resetVerticalPanelPosition();
         }
@@ -1806,7 +1807,7 @@
     @Override
     public void onRtlPropertiesChanged(int layoutDirection) {
         if (layoutDirection != mOldLayoutDirection) {
-            mAfforanceHelper.onRtlPropertiesChanged();
+            mAffordanceHelper.onRtlPropertiesChanged();
             mOldLayoutDirection = layoutDirection;
         }
     }
@@ -1938,7 +1939,7 @@
             return;
         }
         mHintAnimationRunning = true;
-        mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() {
+        mAffordanceHelper.startHintAnimation(rightIcon, new Runnable() {
             @Override
             public void run() {
                 mHintAnimationRunning = false;
@@ -2351,7 +2352,7 @@
         } else {
             animate = false;
         }
-        mAfforanceHelper.launchAffordance(animate, getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+        mAffordanceHelper.launchAffordance(animate, getLayoutDirection() == LAYOUT_DIRECTION_RTL);
     }
 
     public void onAffordanceLaunchEnded() {
@@ -2397,7 +2398,7 @@
                 ? null : resolveInfo.activityInfo.packageName;
         return packageToLaunch != null &&
                (keyguardIsShowing || !isForegroundApp(packageToLaunch)) &&
-               !mAfforanceHelper.isSwipingInProgress();
+               !mAffordanceHelper.isSwipingInProgress();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 18e394e..5f67468 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -60,7 +60,7 @@
         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
     }
 
-    protected PhoneStatusBar mStatusBar;
+    protected StatusBar mStatusBar;
     protected HeadsUpManager mHeadsUpManager;
 
     private float mPeekHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c80b3ad..7e08812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -30,10 +30,10 @@
 
 public class PhoneStatusBarView extends PanelBar {
     private static final String TAG = "PhoneStatusBarView";
-    private static final boolean DEBUG = PhoneStatusBar.DEBUG;
+    private static final boolean DEBUG = StatusBar.DEBUG;
     private static final boolean DEBUG_GESTURES = false;
 
-    PhoneStatusBar mBar;
+    StatusBar mBar;
 
     boolean mIsFullyOpenedPanel = false;
     private final PhoneStatusBarTransitions mBarTransitions;
@@ -59,7 +59,7 @@
         return mBarTransitions;
     }
 
-    public void setBar(PhoneStatusBar bar) {
+    public void setBar(StatusBar bar) {
         mBar = bar;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 2f76cb1..dd567e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -71,7 +71,7 @@
     public static final String TILES_SETTING = Secure.QS_TILES;
 
     private final Context mContext;
-    private final PhoneStatusBar mStatusBar;
+    private final StatusBar mStatusBar;
     private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
     protected final ArrayList<String> mTileSpecs = new ArrayList<>();
     private final TileServices mServices;
@@ -81,7 +81,7 @@
     private final StatusBarIconController mIconController;
     private int mCurrentUser;
 
-    public QSTileHost(Context context, PhoneStatusBar statusBar,
+    public QSTileHost(Context context, StatusBar statusBar,
             StatusBarIconController iconController) {
         mIconController = iconController;
         mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 7d82477..018d888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -72,9 +72,7 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -127,8 +125,6 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.SystemUIApplication;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.classifier.FalsingLog;
@@ -142,6 +138,7 @@
 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.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.recents.ScreenPinningRequest;
@@ -152,7 +149,6 @@
 import com.android.systemui.stackdivider.WindowManagerProxy;
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.BackDropView;
-import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.DragDownHelper;
@@ -161,9 +157,13 @@
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationContentView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationInfo;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
@@ -172,7 +172,6 @@
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
-import com.android.systemui.statusbar.policy.BatteryControllerImpl;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -184,11 +183,9 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
 
@@ -198,17 +195,102 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
+import android.app.ActivityManager.StackId;
+import android.app.INotificationManager;
+import android.app.KeyguardManager;
+import android.app.NotificationChannel;
+import android.app.RemoteInput;
+import android.app.TaskStackBuilder;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.os.Build;
+import android.os.Handler;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.service.notification.NotificationListenerService;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.view.IWindowManager;
+import android.view.ViewAnimationUtils;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.RecentsComponent;
+import com.android.systemui.SwipeHelper;
+import com.android.systemui.SystemUI;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeGutsContent;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+import com.android.systemui.util.NotificationChannels;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.Stack;
+
+public class StatusBar extends SystemUI implements DemoMode,
         DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
-        OnHeadsUpChangedListener, VisualStabilityManager.Callback, SnoozeListener {
-    static final String TAG = "PhoneStatusBar";
-    public static final boolean DEBUG = BaseStatusBar.DEBUG;
+        OnHeadsUpChangedListener, VisualStabilityManager.Callback, SnoozeListener,
+        CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
+        ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
+        ExpandableNotificationRow.OnExpandClickListener {
+    public static final boolean MULTIUSER_DEBUG = false;
+
+    public static final boolean ENABLE_REMOTE_INPUT =
+            SystemProperties.getBoolean("debug.enable_remote_input", true);
+    public static final boolean ENABLE_CHILD_NOTIFICATIONS
+            = SystemProperties.getBoolean("debug.child_notifs", true);
+    public static final boolean FORCE_REMOTE_INPUT_HISTORY =
+            SystemProperties.getBoolean("debug.force_remoteinput_history", false);
+    private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
+
+    protected static final int MSG_SHOW_RECENT_APPS = 1019;
+    protected static final int MSG_HIDE_RECENT_APPS = 1020;
+    protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
+    protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
+    protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
+    protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
+    protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
+    protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
+    protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
+
+    protected static final boolean ENABLE_HEADS_UP = true;
+    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+
+    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
+    // Should match the values in PhoneWindowManager
+    public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+    public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
+
+    private static final String BANNER_ACTION_CANCEL =
+            "com.android.systemui.statusbar.banner_action_cancel";
+    private static final String BANNER_ACTION_SETUP =
+            "com.android.systemui.statusbar.banner_action_setup";
+    private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
+            = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
+    static final String TAG = "StatusBar";
+    public static final boolean DEBUG = false;
     public static final boolean SPEW = false;
     public static final boolean DUMPTRUCK = true; // extra dumpsys info
     public static final boolean DEBUG_GESTURES = false;
@@ -300,6 +382,14 @@
         FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement;
     }
 
+    /**
+     * The {@link StatusBarState} of the status bar.
+     */
+    protected int mState;
+    protected boolean mBouncerShowing;
+    protected boolean mShowLockscreenNotifications;
+    protected boolean mAllowLockscreenRemoteInput;
+
     PhoneStatusBarPolicy mIconPolicy;
 
     VolumeComponent mVolumeComponent;
@@ -310,7 +400,6 @@
 
     int mNaturalBarHeight = -1;
 
-    Display mDisplay;
     Point mCurrentDisplaySize = new Point();
 
     protected StatusBarWindowView mStatusBarWindow;
@@ -416,6 +505,7 @@
         }
     };
 
+    protected H mHandler = createHandler();
     final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange) {
@@ -523,6 +613,8 @@
     private Vibrator mVibrator;
     private long[] mCameraLaunchGestureVibePattern;
 
+    private final int[] mTmpInt2 = new int[2];
+
     // Fingerprint (as computed by getLoggingFingerprint() of the last logged state.
     private int mLastLoggedStateFingerprint;
 
@@ -614,7 +706,6 @@
     private NetworkController mNetworkController;
     private KeyguardMonitorImpl mKeyguardMonitor;
     private BatteryController mBatteryController;
-    private DeviceProvisionedController mDeviceProvisionedController;
 
     private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
         final int N = array.size();
@@ -626,6 +717,7 @@
 
     private final View.OnClickListener mGoToLockedShadeListener = v -> {
         if (mState == StatusBarState.KEYGUARD) {
+            wakeUpIfDozing(SystemClock.uptimeMillis(), v);
             goToLockedShade(null);
         }
     };
@@ -656,16 +748,156 @@
         mAssistManager = Dependency.get(AssistManager.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
 
-        mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
-                .getDefaultDisplay();
+        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        mDisplay = mWindowManager.getDefaultDisplay();
         updateDisplaySize();
         mScrimSrcModeEnabled = mContext.getResources().getBoolean(
                 R.bool.config_status_bar_scrim_behind_use_src);
 
         DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
-        putComponent(PhoneStatusBar.class, this);
+        putComponent(StatusBar.class, this);
 
-        super.start(); // calls createAndAddWindows()
+        // start old BaseStatusBar.start().
+        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
+        mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService(
+                Context.DEVICE_POLICY_SERVICE);
+
+        mNotificationData = new NotificationData(this);
+
+        mAccessibilityManager = (AccessibilityManager)
+                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+        mDreamManager = IDreamManager.Stub.asInterface(
+                ServiceManager.checkService(DreamService.DREAM_SERVICE));
+        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+
+        mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+                mSettingsObserver);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+                mLockscreenSettingsObserver,
+                UserHandle.USER_ALL);
+        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT),
+                    false,
+                    mSettingsObserver,
+                    UserHandle.USER_ALL);
+        }
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                true,
+                mLockscreenSettingsObserver,
+                UserHandle.USER_ALL);
+
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+
+        mRecents = getComponent(Recents.class);
+
+        final Configuration currentConfig = mContext.getResources().getConfiguration();
+        mLocale = currentConfig.locale;
+        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
+        mFontScale = currentConfig.fontScale;
+        mDensity = currentConfig.densityDpi;
+
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+        mLockPatternUtils = new LockPatternUtils(mContext);
+
+        // Connect in to the status bar manager service
+        mCommandQueue = getComponent(CommandQueue.class);
+        mCommandQueue.addCallbacks(this);
+
+        int[] switches = new int[9];
+        ArrayList<IBinder> binders = new ArrayList<IBinder>();
+        ArrayList<String> iconSlots = new ArrayList<>();
+        ArrayList<StatusBarIcon> icons = new ArrayList<>();
+        Rect fullscreenStackBounds = new Rect();
+        Rect dockedStackBounds = new Rect();
+        try {
+            mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
+                    fullscreenStackBounds, dockedStackBounds);
+        } catch (RemoteException ex) {
+            // If the system process isn't there we're doomed anyway.
+        }
+
+        createAndAddWindows();
+
+        mSettingsObserver.onChange(false); // set up
+        disable(switches[0], switches[6], false /* animate */);
+        setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
+                fullscreenStackBounds, dockedStackBounds);
+        topAppWindowChanged(switches[2] != 0);
+        // StatusBarManagerService has a back up of IME token and it's restored here.
+        setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0);
+
+        // Set up the initial icon state
+        int N = iconSlots.size();
+        int viewIndex = 0;
+        for (int i=0; i < N; i++) {
+            setIcon(iconSlots.get(i), icons.get(i));
+        }
+
+        // Set up the initial notification state.
+        try {
+            mNotificationListener.registerAsSystemService(mContext,
+                    new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
+                    UserHandle.USER_ALL);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to register notification listener", e);
+        }
+
+
+        if (DEBUG) {
+            Log.d(TAG, String.format(
+                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
+                   icons.size(),
+                   switches[0],
+                   switches[1],
+                   switches[2],
+                   switches[3]
+                   ));
+        }
+
+        mCurrentUserId = ActivityManager.getCurrentUser();
+        setHeadsUpUser(mCurrentUserId);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_ADDED);
+        filter.addAction(Intent.ACTION_USER_PRESENT);
+        mContext.registerReceiver(mBaseBroadcastReceiver, filter);
+
+        IntentFilter internalFilter = new IntentFilter();
+        internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
+        internalFilter.addAction(BANNER_ACTION_CANCEL);
+        internalFilter.addAction(BANNER_ACTION_SETUP);
+        mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null);
+
+        IntentFilter allUsersFilter = new IntentFilter();
+        allUsersFilter.addAction(
+                DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+        allUsersFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
+        mContext.registerReceiverAsUser(mAllUsersReceiver, UserHandle.ALL, allUsersFilter,
+                null, null);
+        updateCurrentProfilesCache();
+
+        IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService("vrmanager"));
+        try {
+            vrManager.registerListener(mVrStateCallbacks);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+        }
+
+        mNonBlockablePkgs = new HashSet<String>();
+        Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray(
+                com.android.internal.R.array.config_nonBlockableNotificationPackages));
+        // end old BaseStatusBar.start().
 
         mMediaSessionManager
                 = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -778,7 +1010,7 @@
         mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
                 R.id.notification_stack_scroller);
         mStackScroller.setLongPressListener(getNotificationLongClicker());
-        mStackScroller.setPhoneStatusBar(this);
+        mStackScroller.setStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
         mStackScroller.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setOnGroupChangeListener(mStackScroller);
@@ -862,7 +1094,7 @@
 
         initEmergencyCryptkeeperText();
 
-        mKeyguardBottomArea.setPhoneStatusBar(this);
+        mKeyguardBottomArea.setStatusBar(this);
         mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
         if (UserManager.get(mContext).isUserSwitcherEnabled()) {
             createUserSwitcher();
@@ -1001,9 +1233,21 @@
         mNotificationShelf.setStatusBarState(mState);
     }
 
-    @Override
     protected void onDensityOrFontScaleChanged() {
-        super.onDensityOrFontScaleChanged();
+        // start old BaseStatusBar.onDensityOrFontScaleChanged().
+        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
+        for (int i = 0; i < activeNotifications.size(); i++) {
+            Entry entry = activeNotifications.get(i);
+            boolean exposedGuts = mNotificationGutsExposed != null
+                    && entry.row.getGuts() == mNotificationGutsExposed;
+            entry.row.reInflateViews();
+            if (exposedGuts) {
+                mNotificationGutsExposed = entry.row.getGuts();
+                bindGuts(entry.row, mGutsMenuItem);
+            }
+            inflateViews(entry, mStackScroller);
+        }
+        // end old BaseStatusBar.onDensityOrFontScaleChanged().
         mScrimController.onDensityOrFontScaleChanged();
         mStatusBarView.onDensityOrFontScaleChanged();
         if (mBrightnessMirrorController != null) {
@@ -1016,7 +1260,7 @@
         inflateEmptyShadeView();
         updateEmptyShadeView();
         mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged();
-        // TODO: Bring these out of PhoneStatusBar.
+        // TODO: Bring these out of StatusBar.
         ((UserInfoControllerImpl) Dependency.get(UserInfoController.class))
                 .onDensityOrFontScaleChanged();
         Dependency.get(UserSwitcherController.class).onDensityOrFontScaleChanged();
@@ -1162,16 +1406,20 @@
         }
     }
 
-    @Override
     protected void setZenMode(int mode) {
-        super.setZenMode(mode);
+        // start old BaseStatusBar.setZenMode().
+        if (isDeviceProvisioned()) {
+            mZenMode = mode;
+            updateNotifications();
+        }
+        // end old BaseStatusBar.setZenMode().
         if (mIconPolicy != null) {
             mIconPolicy.setZenMode(mode);
         }
     }
 
     protected void startKeyguard() {
-        Trace.beginSection("PhoneStatusBar#startKeyguard");
+        Trace.beginSection("StatusBar#startKeyguard");
         KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
         mFingerprintUnlockController = new FingerprintUnlockController(mContext,
                 mStatusBarWindowManager, mDozeScrimController, keyguardViewMediator,
@@ -1211,7 +1459,6 @@
         Trace.endSection();
     }
 
-    @Override
     protected View getStatusBarView() {
         return mStatusBarView;
     }
@@ -1233,7 +1480,6 @@
         return mNaturalBarHeight;
     }
 
-    @Override
     protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
         if (mRecents == null) {
             return false;
@@ -1281,7 +1527,6 @@
         return new UserHandle(mCurrentUserId);
     }
 
-    @Override
     public void addNotification(StatusBarNotification notification, RankingMap ranking,
             Entry oldEntry) {
         if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
@@ -1344,13 +1589,11 @@
         }
     }
 
-    @Override
     protected void updateNotificationRanking(RankingMap ranking) {
         mNotificationData.updateRanking(ranking);
         updateNotifications();
     }
 
-    @Override
     public void removeNotification(String key, RankingMap ranking) {
         boolean deferRemoval = false;
         if (mHeadsUpManager.isHeadsUp(key)) {
@@ -1481,13 +1724,28 @@
         }
     }
 
-    @Override
     protected void performRemoveNotification(StatusBarNotification n) {
         Entry entry = mNotificationData.get(n.getKey());
         if (mRemoteInputController.isRemoteInputActive(entry)) {
             mRemoteInputController.removeRemoteInput(entry, null);
         }
-        super.performRemoveNotification(n);
+        // start old BaseStatusBar.performRemoveNotification.
+        final String pkg = n.getPackageName();
+        final String tag = n.getTag();
+        final int id = n.getId();
+        final int userId = n.getUserId();
+        try {
+            mBarService.onNotificationClear(pkg, tag, id, userId);
+            if (FORCE_REMOTE_INPUT_HISTORY
+                    && mKeysKeptForRemoteInput.contains(n.getKey())) {
+                mKeysKeptForRemoteInput.remove(n.getKey());
+            }
+            removeNotification(n.getKey(), null);
+
+        } catch (RemoteException ex) {
+            // system process is dead if we're here.
+        }
+        // end old BaseStatusBar.performRemoveNotification.
     }
 
     private void updateNotificationShade() {
@@ -1716,17 +1974,14 @@
         }
     }
 
-    @Override
     public void addQsTile(ComponentName tile) {
         mQSPanel.getHost().addTile(tile);
     }
 
-    @Override
     public void remQsTile(ComponentName tile) {
         mQSPanel.getHost().removeTile(tile);
     }
 
-    @Override
     public void clickTile(ComponentName tile) {
         mQSPanel.clickTile(tile);
     }
@@ -1789,7 +2044,6 @@
         return entry.row.getParent() instanceof NotificationStackScrollLayout;
     }
 
-    @Override
     protected void updateNotifications() {
         mNotificationData.filterAndSort();
 
@@ -1800,7 +2054,6 @@
         updateNotifications();
     }
 
-    @Override
     protected void setAreThereNotifications() {
 
         if (SPEW) {
@@ -1984,7 +2237,7 @@
      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
      */
     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
-        Trace.beginSection("PhoneStatusBar#updateMediaMetaData");
+        Trace.beginSection("StatusBar#updateMediaMetaData");
         if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
             Trace.endSection();
             return;
@@ -2279,9 +2532,8 @@
         disable(mDisabledUnmodified1, mDisabledUnmodified2, animate);
     }
 
-    @Override
-    protected BaseStatusBar.H createHandler() {
-        return new PhoneStatusBar.H();
+    protected H createHandler() {
+        return new StatusBar.H();
     }
 
     @Override
@@ -2318,7 +2570,6 @@
         return getBarState() == StatusBarState.KEYGUARD;
     }
 
-    @Override
     public boolean isDozing() {
         return mDozing;
     }
@@ -2412,7 +2663,6 @@
 
     }
 
-    @Override
     protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
             boolean alertAgain) {
         final boolean wasHeadsUp = isHeadsUp(key);
@@ -2429,7 +2679,6 @@
         }
     }
 
-    @Override
     protected void setHeadsUpUser(int newUserId) {
         if (mHeadsUpManager != null) {
             mHeadsUpManager.setUser(newUserId);
@@ -2440,7 +2689,6 @@
         return mHeadsUpManager.isHeadsUp(key);
     }
 
-    @Override
     protected boolean isSnoozedPackage(StatusBarNotification sbn) {
         return mHeadsUpManager.isSnoozed(sbn.getPackageName());
     }
@@ -2497,11 +2745,39 @@
     /**
      * All changes to the status bar and notifications funnel through here and are batched.
      */
-    private class H extends BaseStatusBar.H {
+    protected class H extends Handler {
         @Override
         public void handleMessage(Message m) {
-            super.handleMessage(m);
             switch (m.what) {
+                // start old BaseStatusBar.H handling.
+                case MSG_SHOW_RECENT_APPS:
+                    showRecents(m.arg1 > 0, m.arg2 != 0);
+                    break;
+                case MSG_HIDE_RECENT_APPS:
+                    hideRecents(m.arg1 > 0, m.arg2 > 0);
+                    break;
+                case MSG_TOGGLE_RECENTS_APPS:
+                    toggleRecents();
+                    break;
+                case MSG_PRELOAD_RECENT_APPS:
+                    preloadRecents();
+                    break;
+                case MSG_CANCEL_PRELOAD_RECENT_APPS:
+                    cancelPreloadingRecents();
+                    break;
+                case MSG_SHOW_NEXT_AFFILIATED_TASK:
+                    showRecentsNextAffiliatedTask();
+                    break;
+                case MSG_SHOW_PREV_AFFILIATED_TASK:
+                    showRecentsPreviousAffiliatedTask();
+                    break;
+                case MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU:
+                    toggleKeyboardShortcuts(m.arg1);
+                    break;
+                case MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU:
+                    dismissKeyboardShortcuts();
+                    break;
+                // End old BaseStatusBar.H handling.
                 case MSG_OPEN_NOTIFICATION_PANEL:
                     animateExpandNotificationsPanel();
                     break;
@@ -2518,7 +2794,6 @@
         }
     }
 
-    @Override
     public void maybeEscalateHeadsUp() {
         Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries();
         for (HeadsUpManager.HeadsUpEntry entry : entries) {
@@ -2618,12 +2893,10 @@
                 1.0f /* speedUpFactor */);
     }
 
-    @Override
     public void animateCollapsePanels(int flags, boolean force) {
         animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
     }
 
-    @Override
     public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
         animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
     }
@@ -2948,7 +3221,6 @@
         }
     };
 
-    @Override
     public void setInteracting(int barWindow, boolean interacting) {
         final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting;
         mInteractingWindows = interacting
@@ -3170,7 +3442,6 @@
         pw.println(BarTransitions.modeToString(transitions.getMode()));
     }
 
-    @Override
     public void createAndAddWindows() {
         addStatusBarWindow();
     }
@@ -3353,7 +3624,6 @@
         }
     }
 
-    @Override
     protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) {
         dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone);
     }
@@ -3373,7 +3643,15 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         updateResources();
         updateDisplaySize(); // populates mDisplayMetrics
-        super.onConfigurationChanged(newConfig); // calls refreshLayout
+        // Begin old BaseStatusBar.onConfigurationChanged
+        final float fontScale = newConfig.fontScale;
+        final int density = newConfig.densityDpi;
+        if (density != mDensity || mFontScale != fontScale) {
+            onDensityOrFontScaleChanged();
+            mDensity = density;
+            mFontScale = fontScale;
+        }
+        // End old BaseStatusBar.onConfigurationChanged
 
         if (DEBUG) {
             Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
@@ -3383,9 +3661,10 @@
         mScreenPinningRequest.onConfigurationChanged();
     }
 
-    @Override
     public void userSwitched(int newUserId) {
-        super.userSwitched(newUserId);
+        // Begin old BaseStatusBar.userSwitched
+        setHeadsUpUser(newUserId);
+        // End old BaseStatusBar.userSwitched
         if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
         animateCollapsePanels();
         updatePublicMode();
@@ -3440,14 +3719,40 @@
 
     // Visibility reporting
 
-    @Override
     protected void handleVisibleToUserChanged(boolean visibleToUser) {
         if (visibleToUser) {
-            super.handleVisibleToUserChanged(visibleToUser);
+            handleVisibleToUserChangedImpl(visibleToUser);
             startNotificationLogging();
         } else {
             stopNotificationLogging();
-            super.handleVisibleToUserChanged(visibleToUser);
+            handleVisibleToUserChangedImpl(visibleToUser);
+        }
+    }
+
+    /**
+     * The LEDs are turned off when the notification panel is shown, even just a little bit.
+     * See also StatusBar.setPanelExpanded for another place where we attempt to do this.
+     */
+    // Old BaseStatusBar.handleVisibileToUserChanged
+    private void handleVisibleToUserChangedImpl(boolean visibleToUser) {
+        try {
+            if (visibleToUser) {
+                boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+                boolean clearNotificationEffects =
+                        !isPanelFullyCollapsed() &&
+                        (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
+                int notificationLoad = mNotificationData.getActiveNotifications().size();
+                if (pinnedHeadsUp && isPanelFullyCollapsed())  {
+                    notificationLoad = 1;
+                } else {
+                    MetricsLogger.histogram(mContext, "note_load", notificationLoad);
+                }
+                mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
+            } else {
+                mBarService.onPanelHidden();
+            }
+        } catch (RemoteException ex) {
+            // Won't fail unless the world has ended.
         }
     }
 
@@ -3638,9 +3943,16 @@
         }
     }
 
-    @Override
     public void destroy() {
-        super.destroy();
+        // Begin old BaseStatusBar.destroy().
+        mContext.unregisterReceiver(mBaseBroadcastReceiver);
+        try {
+            mNotificationListener.unregisterAsSystemService();
+        } catch (RemoteException e) {
+            // Ignore.
+        }
+        mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+        // End old BaseStatusBar.destroy().
         if (mStatusBarWindow != null) {
             mWindowManager.removeViewImmediate(mStatusBarWindow);
             mStatusBarWindow = null;
@@ -3745,7 +4057,6 @@
         return mState;
     }
 
-    @Override
     public boolean isPanelFullyCollapsed() {
         return mNotificationPanel.isFullyCollapsed();
     }
@@ -3794,12 +4105,10 @@
         updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
-    @Override
     public boolean isCollapsing() {
         return mNotificationPanel.isCollapsing();
     }
 
-    @Override
     public void addPostCollapseAction(Runnable r) {
         mPostCollapseRunnables.add(r);
     }
@@ -3913,7 +4222,7 @@
      * @return true if we would like to stay in the shade, false if it should go away entirely
      */
     public boolean hideKeyguard() {
-        Trace.beginSection("PhoneStatusBar#hideKeyguard");
+        Trace.beginSection("StatusBar#hideKeyguard");
         boolean staying = mLeaveOpenOnKeyguardHide;
         setBarState(StatusBarState.SHADE);
         View viewToClick = null;
@@ -4045,7 +4354,7 @@
     }
 
     protected void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) {
-        Trace.beginSection("PhoneStatusBar#updateKeyguardState");
+        Trace.beginSection("StatusBar#updateKeyguardState");
         if (mState == StatusBarState.KEYGUARD) {
             mKeyguardIndicationController.setVisible(true);
             mNotificationPanel.resetViews();
@@ -4082,7 +4391,7 @@
     }
 
     private void updateDozingState() {
-        Trace.beginSection("PhoneStatusBar#updateDozingState");
+        Trace.beginSection("StatusBar#updateDozingState");
         boolean animate = !mDozing && mDozeScrimController.isPulsing();
         mNotificationPanel.setDozing(mDozing, animate);
         mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
@@ -4288,7 +4597,6 @@
         }
     }
 
-    @Override
     protected int getMaxKeyguardNotifications(boolean recompute) {
         if (recompute) {
             mMaxKeyguardNotifications = Math.max(1,
@@ -4388,29 +4696,63 @@
         }
     }
 
-    @Override
     public void onLockedNotificationImportanceChange(OnDismissAction dismissAction) {
         mLeaveOpenOnKeyguardHide = true;
         dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */);
     }
 
-    @Override
     protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
         mLeaveOpenOnKeyguardHide = true;
         showBouncer();
         mPendingRemoteInputView = clicked;
     }
 
-    @Override
+    protected void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
+            View clickedView) {
+        if (isKeyguardShowing()) {
+            onLockedRemoteInput(row, clickedView);
+        } else {
+            row.setUserExpanded(true);
+            row.getPrivateLayout().setOnExpandedVisibleListener(clickedView::performClick);
+        }
+    }
+
     protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender,
             String notificationKey) {
         // Clear pending remote view, as we do not want to trigger pending remote input view when
         // it's called by other code
         mPendingWorkRemoteInputView = null;
-        return super.startWorkChallengeIfNecessary(userId, intendSender, notificationKey);
+        // Begin old BaseStatusBar.startWorkChallengeIfNecessary.
+        final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null,
+                null, userId);
+        if (newIntent == null) {
+            return false;
+        }
+        final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
+        callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender);
+        callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey);
+        callBackIntent.setPackage(mContext.getPackageName());
+
+        PendingIntent callBackPendingIntent = PendingIntent.getBroadcast(
+                mContext,
+                0,
+                callBackIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT |
+                        PendingIntent.FLAG_ONE_SHOT |
+                        PendingIntent.FLAG_IMMUTABLE);
+        newIntent.putExtra(
+                Intent.EXTRA_INTENT,
+                callBackPendingIntent.getIntentSender());
+        try {
+            ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent,
+                    null /*options*/);
+        } catch (RemoteException ex) {
+            // ignore
+        }
+        return true;
+        // End old BaseStatusBar.startWorkChallengeIfNecessary.
     }
 
-    @Override
     protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
             View clicked) {
         // Collapse notification and show work challenge
@@ -4430,7 +4772,6 @@
         return false;
     }
 
-    @Override
     protected void onWorkChallengeChanged() {
         updatePublicMode();
         updateNotifications();
@@ -4527,9 +4868,8 @@
         return mKeyguardFadingAwayDuration;
     }
 
-    @Override
     public void setBouncerShowing(boolean bouncerShowing) {
-        super.setBouncerShowing(bouncerShowing);
+        mBouncerShowing = bouncerShowing;
         mStatusBarView.setBouncerShowing(bouncerShowing);
         recomputeDisableFlags(true /* animate */);
     }
@@ -4609,13 +4949,14 @@
         return !mNotificationData.getActiveNotifications().isEmpty();
     }
 
-    @Override
-    public void wakeUpIfDozing(long time, PointF where) {
+    public void wakeUpIfDozing(long time, View where) {
         if (mDozing && mDozeScrimController.isPulsing()) {
             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
             pm.wakeUp(time, "com.android.systemui:NODOZE");
             mWakeUpComingFromTouch = true;
-            mWakeUpTouchLocation = where;
+            where.getLocationInWindow(mTmpInt2);
+            mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2,
+                    mTmpInt2[1] + where.getHeight() / 2);
             mNotificationPanel.setTouchDisabled(false);
             mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
             mFalsingManager.onScreenOnFromTouch();
@@ -4703,7 +5044,7 @@
     }
 
     private void updateDozing() {
-        Trace.beginSection("PhoneStatusBar#updateDozing");
+        Trace.beginSection("StatusBar#updateDozing");
         // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
         mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
                 || mFingerprintUnlockController.getMode()
@@ -4855,18 +5196,2033 @@
 
         @Override
         public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
-            PhoneStatusBar.this.startPendingIntentDismissingKeyguard(intent);
+            StatusBar.this.startPendingIntentDismissingKeyguard(intent);
         }
 
     }
 
-    @Override
     public SnoozeListener getSnoozeListener() {
         return this;
     }
 
     @Override
-    public void snoozeNotification(StatusBarNotification sbn, long snoozeUntil) {
-        setNotificationSnoozed(sbn, snoozeUntil);
+    public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption) {
+        setNotificationSnoozed(sbn, snoozeOption);
     }
+
+    // Begin Extra BaseStatusBar methods.
+
+    protected CommandQueue mCommandQueue;
+    protected IStatusBarService mBarService;
+
+    // all notifications
+    protected NotificationData mNotificationData;
+    protected NotificationStackScrollLayout mStackScroller;
+
+    protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
+
+    protected RemoteInputController mRemoteInputController;
+
+    // for heads up notifications
+    protected HeadsUpManager mHeadsUpManager;
+
+    // handling reordering
+    protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
+
+    protected int mCurrentUserId = 0;
+    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+
+    protected int mLayoutDirection = -1; // invalid
+    protected AccessibilityManager mAccessibilityManager;
+
+    protected boolean mDeviceInteractive;
+
+    protected boolean mVisible;
+    protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
+    protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
+
+    /**
+     * Notifications with keys in this set are not actually around anymore. We kept them around
+     * when they were canceled in response to a remote input interaction. This allows us to show
+     * what you replied and allows you to continue typing into it.
+     */
+    protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
+
+    // mScreenOnFromKeyguard && mVisible.
+    private boolean mVisibleToUser;
+
+    private Locale mLocale;
+    private float mFontScale;
+
+    protected boolean mUseHeadsUp = false;
+    protected boolean mHeadsUpTicker = false;
+    protected boolean mDisableNotificationAlerts = false;
+
+    protected DevicePolicyManager mDevicePolicyManager;
+    protected IDreamManager mDreamManager;
+    protected PowerManager mPowerManager;
+    protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+
+    // public mode, private notifications, etc
+    private final SparseBooleanArray mLockscreenPublicMode = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+
+    private UserManager mUserManager;
+    private int mDensity;
+
+    protected KeyguardManager mKeyguardManager;
+    private LockPatternUtils mLockPatternUtils;
+    private DeviceProvisionedController mDeviceProvisionedController;
+
+    // UI-specific methods
+
+    protected WindowManager mWindowManager;
+    protected IWindowManager mWindowManagerService;
+
+    protected Display mDisplay;
+
+    protected RecentsComponent mRecents;
+
+    protected int mZenMode;
+
+    // which notification is currently being longpress-examined by the user
+    private NotificationGuts mNotificationGutsExposed;
+    private MenuItem mGutsMenuItem;
+
+    private KeyboardShortcuts mKeyboardShortcuts;
+
+    protected NotificationShelf mNotificationShelf;
+    protected DismissView mDismissView;
+    protected EmptyShadeView mEmptyShadeView;
+
+    private NotificationClicker mNotificationClicker = new NotificationClicker();
+
+    protected AssistManager mAssistManager;
+
+    protected boolean mVrMode;
+
+    private Set<String> mNonBlockablePkgs;
+
+    @Override  // NotificationData.Environment
+    public boolean isDeviceProvisioned() {
+        return mDeviceProvisionedController.isDeviceProvisioned();
+    }
+
+    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+        @Override
+        public void onVrStateChanged(boolean enabled) {
+            mVrMode = enabled;
+        }
+    };
+
+    public boolean isDeviceInVrMode() {
+        return mVrMode;
+    }
+
+    private final DeviceProvisionedListener mDeviceProvisionedListener =
+            new DeviceProvisionedListener() {
+        @Override
+        public void onDeviceProvisionedChanged() {
+            updateNotifications();
+        }
+    };
+
+    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
+        @Override
+        public void onChange(boolean selfChange) {
+            final int mode = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+            setZenMode(mode);
+
+            updateLockscreenNotificationSetting();
+        }
+    };
+
+    private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
+        @Override
+        public void onChange(boolean selfChange) {
+            // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+            // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
+            mUsersAllowingPrivateNotifications.clear();
+            mUsersAllowingNotifications.clear();
+            // ... and refresh all the notifications
+            updateLockscreenNotificationSetting();
+            updateNotifications();
+        }
+    };
+
+    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
+
+        @Override
+        public boolean onClickHandler(
+                final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
+            wakeUpIfDozing(SystemClock.uptimeMillis(), view);
+
+
+            if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
+                return true;
+            }
+
+            if (DEBUG) {
+                Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
+            }
+            logActionClick(view);
+            // The intent we are sending is for the application, which
+            // won't have permission to immediately start an activity after
+            // the user switches to home.  We know it is safe to do at this
+            // point, so make sure new activity switches are now allowed.
+            try {
+                ActivityManager.getService().resumeAppSwitches();
+            } catch (RemoteException e) {
+            }
+            final boolean isActivity = pendingIntent.isActivity();
+            if (isActivity) {
+                final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+                final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
+                        mContext, pendingIntent.getIntent(), mCurrentUserId);
+                dismissKeyguardThenExecute(new OnDismissAction() {
+                    @Override
+                    public boolean onDismiss() {
+                        try {
+                            ActivityManager.getService().resumeAppSwitches();
+                        } catch (RemoteException e) {
+                        }
+
+                        boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
+
+                        // close the shade if it was open
+                        if (handled) {
+                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                                    true /* force */);
+                            visibilityChanged(false);
+                            mAssistManager.hideAssist();
+                        }
+
+                        // Wait for activity start.
+                        return handled;
+                    }
+                }, afterKeyguardGone);
+                return true;
+            } else {
+                return superOnClickHandler(view, pendingIntent, fillInIntent);
+            }
+        }
+
+        private void logActionClick(View view) {
+            ViewParent parent = view.getParent();
+            String key = getNotificationKeyForParent(parent);
+            if (key == null) {
+                Log.w(TAG, "Couldn't determine notification for click.");
+                return;
+            }
+            int index = -1;
+            // If this is a default template, determine the index of the button.
+            if (view.getId() == com.android.internal.R.id.action0 &&
+                    parent != null && parent instanceof ViewGroup) {
+                ViewGroup actionGroup = (ViewGroup) parent;
+                index = actionGroup.indexOfChild(view);
+            }
+            try {
+                mBarService.onNotificationActionClick(key, index);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        }
+
+        private String getNotificationKeyForParent(ViewParent parent) {
+            while (parent != null) {
+                if (parent instanceof ExpandableNotificationRow) {
+                    return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey();
+                }
+                parent = parent.getParent();
+            }
+            return null;
+        }
+
+        private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
+                Intent fillInIntent) {
+            return super.onClickHandler(view, pendingIntent, fillInIntent,
+                    StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+        }
+
+        private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
+            Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
+            RemoteInput[] inputs = null;
+            if (tag instanceof RemoteInput[]) {
+                inputs = (RemoteInput[]) tag;
+            }
+
+            if (inputs == null) {
+                return false;
+            }
+
+            RemoteInput input = null;
+
+            for (RemoteInput i : inputs) {
+                if (i.getAllowFreeFormInput()) {
+                    input = i;
+                }
+            }
+
+            if (input == null) {
+                return false;
+            }
+
+            ViewParent p = view.getParent();
+            RemoteInputView riv = null;
+            while (p != null) {
+                if (p instanceof View) {
+                    View pv = (View) p;
+                    if (pv.isRootNamespace()) {
+                        riv = findRemoteInputView(pv);
+                        break;
+                    }
+                }
+                p = p.getParent();
+            }
+            ExpandableNotificationRow row = null;
+            while (p != null) {
+                if (p instanceof ExpandableNotificationRow) {
+                    row = (ExpandableNotificationRow) p;
+                    break;
+                }
+                p = p.getParent();
+            }
+
+            if (row == null) {
+                return false;
+            }
+
+            row.setUserExpanded(true);
+
+            if (!mAllowLockscreenRemoteInput) {
+                final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
+                if (isLockscreenPublicMode(userId)) {
+                    onLockedRemoteInput(row, view);
+                    return true;
+                }
+                if (mUserManager.getUserInfo(userId).isManagedProfile()
+                        && mKeyguardManager.isDeviceLocked(userId)) {
+                    onLockedWorkRemoteInput(userId, row, view);
+                    return true;
+                }
+            }
+
+            if (riv == null) {
+                riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
+                if (riv == null) {
+                    return false;
+                }
+                if (!row.getPrivateLayout().getExpandedChild().isShown()) {
+                    onMakeExpandedVisibleForRemoteInput(row, view);
+                    return true;
+                }
+            }
+
+            int width = view.getWidth();
+            if (view instanceof TextView) {
+                // Center the reveal on the text which might be off-center from the TextView
+                TextView tv = (TextView) view;
+                if (tv.getLayout() != null) {
+                    int innerWidth = (int) tv.getLayout().getLineWidth(0);
+                    innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+                    width = Math.min(width, innerWidth);
+                }
+            }
+            int cx = view.getLeft() + width / 2;
+            int cy = view.getTop() + view.getHeight() / 2;
+            int w = riv.getWidth();
+            int h = riv.getHeight();
+            int r = Math.max(
+                    Math.max(cx + cy, cx + (h - cy)),
+                    Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+            riv.setRevealParameters(cx, cy, r);
+            riv.setPendingIntent(pendingIntent);
+            riv.setRemoteInput(inputs, input);
+            riv.focusAnimated();
+
+            return true;
+        }
+
+        private RemoteInputView findRemoteInputView(View v) {
+            if (v == null) {
+                return null;
+            }
+            return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+        }
+    };
+
+    private final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                updateCurrentProfilesCache();
+                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+
+                updateLockscreenNotificationSetting();
+
+                userSwitched(mCurrentUserId);
+            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+                updateCurrentProfilesCache();
+            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+                List<ActivityManager.RecentTaskInfo> recentTask = null;
+                try {
+                    recentTask = ActivityManager.getService().getRecentTasks(1,
+                            ActivityManager.RECENT_WITH_EXCLUDED
+                            | ActivityManager.RECENT_INCLUDE_PROFILES,
+                            mCurrentUserId).getList();
+                } catch (RemoteException e) {
+                    // Abandon hope activity manager not running.
+                }
+                if (recentTask != null && recentTask.size() > 0) {
+                    UserInfo user = mUserManager.getUserInfo(recentTask.get(0).userId);
+                    if (user != null && user.isManagedProfile()) {
+                        Toast toast = Toast.makeText(mContext,
+                                R.string.managed_profile_foreground_toast,
+                                Toast.LENGTH_SHORT);
+                        TextView text = (TextView) toast.getView().findViewById(
+                                android.R.id.message);
+                        text.setCompoundDrawablesRelativeWithIntrinsicBounds(
+                                R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
+                        int paddingPx = mContext.getResources().getDimensionPixelSize(
+                                R.dimen.managed_profile_toast_padding);
+                        text.setCompoundDrawablePadding(paddingPx);
+                        toast.show();
+                    }
+                }
+            } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
+                NotificationManager noMan = (NotificationManager)
+                        mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                noMan.cancel(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS);
+
+                Settings.Secure.putInt(mContext.getContentResolver(),
+                        Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
+                if (BANNER_ACTION_SETUP.equals(action)) {
+                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                            true /* force */);
+                    mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
+                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+                    );
+                }
+            } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
+                final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+                final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+                if (intentSender != null) {
+                    try {
+                        mContext.startIntentSender(intentSender, null, 0, 0, 0);
+                    } catch (IntentSender.SendIntentException e) {
+                        /* ignore */
+                    }
+                }
+                if (notificationKey != null) {
+                    try {
+                        mBarService.onNotificationClick(notificationKey);
+                    } catch (RemoteException e) {
+                        /* ignore */
+                    }
+                }
+            }
+        }
+    };
+
+    private final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+            if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
+                    isCurrentProfile(getSendingUserId())) {
+                mUsersAllowingPrivateNotifications.clear();
+                updateLockscreenNotificationSetting();
+                updateNotifications();
+            } else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
+                if (userId != mCurrentUserId && isCurrentProfile(userId)) {
+                    onWorkChallengeChanged();
+                }
+            }
+        }
+    };
+
+    private final NotificationListenerService mNotificationListener =
+            new NotificationListenerService() {
+        @Override
+        public void onListenerConnected() {
+            if (DEBUG) Log.d(TAG, "onListenerConnected");
+            final StatusBarNotification[] notifications = getActiveNotifications();
+            if (notifications == null) {
+                Log.w(TAG, "onListenerConnected unable to get active notifications.");
+                return;
+            }
+            final RankingMap currentRanking = getCurrentRanking();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    for (StatusBarNotification sbn : notifications) {
+                        addNotification(sbn, currentRanking, null /* oldEntry */);
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onNotificationPosted(final StatusBarNotification sbn,
+                final RankingMap rankingMap) {
+            if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
+            if (sbn != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        processForRemoteInput(sbn.getNotification());
+                        String key = sbn.getKey();
+                        mKeysKeptForRemoteInput.remove(key);
+                        boolean isUpdate = mNotificationData.get(key) != null;
+                        // In case we don't allow child notifications, we ignore children of
+                        // notifications that have a summary, since we're not going to show them
+                        // anyway. This is true also when the summary is canceled,
+                        // because children are automatically canceled by NoMan in that case.
+                        if (!ENABLE_CHILD_NOTIFICATIONS
+                            && mGroupManager.isChildInGroupWithSummary(sbn)) {
+                            if (DEBUG) {
+                                Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+                            }
+
+                            // Remove existing notification to avoid stale data.
+                            if (isUpdate) {
+                                removeNotification(key, rankingMap);
+                            } else {
+                                mNotificationData.updateRanking(rankingMap);
+                            }
+                            return;
+                        }
+                        if (isUpdate) {
+                            updateNotification(sbn, rankingMap);
+                        } else {
+                            addNotification(sbn, rankingMap, null /* oldEntry */);
+                        }
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void onNotificationRemoved(StatusBarNotification sbn,
+                final RankingMap rankingMap) {
+            if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
+            if (sbn != null) {
+                final String key = sbn.getKey();
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        removeNotification(key, rankingMap);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void onNotificationRankingUpdate(final RankingMap rankingMap) {
+            if (DEBUG) Log.d(TAG, "onRankingUpdate");
+            if (rankingMap != null) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    updateNotificationRanking(rankingMap);
+                }
+            });
+        }                            }
+
+    };
+
+    private void updateCurrentProfilesCache() {
+        synchronized (mCurrentProfiles) {
+            mCurrentProfiles.clear();
+            if (mUserManager != null) {
+                for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
+                    mCurrentProfiles.put(user.id, user);
+                }
+            }
+        }
+    }
+
+    protected void notifyUserAboutHiddenNotifications() {
+        if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) {
+            Log.d(TAG, "user hasn't seen notification about hidden notifications");
+            if (!mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
+                Log.d(TAG, "insecure lockscreen, skipping notification");
+                Settings.Secure.putInt(mContext.getContentResolver(),
+                        Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
+                return;
+            }
+            Log.d(TAG, "disabling lockecreen notifications and alerting the user");
+            // disable lockscreen notifications until user acts on the banner.
+            Settings.Secure.putInt(mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+            Settings.Secure.putInt(mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0);
+
+            final String packageName = mContext.getPackageName();
+            PendingIntent cancelIntent = PendingIntent.getBroadcast(mContext, 0,
+                    new Intent(BANNER_ACTION_CANCEL).setPackage(packageName),
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+            PendingIntent setupIntent = PendingIntent.getBroadcast(mContext, 0,
+                    new Intent(BANNER_ACTION_SETUP).setPackage(packageName),
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+
+            final int colorRes = com.android.internal.R.color.system_notification_accent_color;
+            Notification.Builder note = new Notification.Builder(mContext)
+                    .setSmallIcon(R.drawable.ic_android)
+                    .setContentTitle(mContext.getString(R.string.hidden_notifications_title))
+                    .setContentText(mContext.getString(R.string.hidden_notifications_text))
+                    .setChannel(NotificationChannels.SECURITY)
+                    .setOngoing(true)
+                    .setColor(mContext.getColor(colorRes))
+                    .setContentIntent(setupIntent)
+                    .addAction(R.drawable.ic_close,
+                            mContext.getString(R.string.hidden_notifications_cancel),
+                            cancelIntent)
+                    .addAction(R.drawable.ic_settings,
+                            mContext.getString(R.string.hidden_notifications_setup),
+                            setupIntent);
+            overrideNotificationAppName(mContext, note);
+
+            NotificationManager noMan =
+                    (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+            noMan.notify(SystemMessage.NOTE_HIDDEN_NOTIFICATIONS, note.build());
+        }
+    }
+
+    @Override  // NotificationData.Environment
+    public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
+        final int thisUserId = mCurrentUserId;
+        final int notificationUserId = n.getUserId();
+        if (DEBUG && MULTIUSER_DEBUG) {
+            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
+                    n, thisUserId, notificationUserId));
+        }
+        return isCurrentProfile(notificationUserId);
+    }
+
+    protected void setNotificationShown(StatusBarNotification n) {
+        setNotificationsShown(new String[]{n.getKey()});
+    }
+
+    protected void setNotificationsShown(String[] keys) {
+        try {
+            mNotificationListener.setNotificationsShown(keys);
+        } catch (RuntimeException e) {
+            Log.d(TAG, "failed setNotificationsShown: ", e);
+        }
+    }
+
+    protected boolean isCurrentProfile(int userId) {
+        synchronized (mCurrentProfiles) {
+            return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
+        }
+    }
+
+    @Override
+    public NotificationGroupManager getGroupManager() {
+        return mGroupManager;
+    }
+
+    protected void bindDismissRunnable(final ExpandableNotificationRow row) {
+        row.setOnDismissRunnable(() -> performRemoveNotification(row.getStatusBarNotification()));
+    }
+
+    protected void applyColorsAndBackgrounds(StatusBarNotification sbn,
+            NotificationData.Entry entry) {
+
+        if (entry.getContentView().getId()
+                != com.android.internal.R.id.status_bar_latest_event_content) {
+            // Using custom RemoteViews
+            if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+                    && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) {
+                entry.row.setShowingLegacyBackground(true);
+                entry.legacy = true;
+            }
+        }
+
+        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+    }
+
+    public boolean isMediaNotification(NotificationData.Entry entry) {
+        // TODO: confirm that there's a valid media key
+        return entry.getExpandedContentView() != null &&
+               entry.getExpandedContentView()
+                       .findViewById(com.android.internal.R.id.media_actions) != null;
+    }
+
+    // The (i) button in the guts that links to the system notification settings for that app
+    private void startAppNotificationSettingsActivity(String packageName, final int appUid) {
+        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
+        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+        startNotificationGutsIntent(intent, appUid);
+    }
+
+    private void startNotificationGutsIntent(final Intent intent, final int appUid) {
+        dismissKeyguardThenExecute(new OnDismissAction() {
+            @Override
+            public boolean onDismiss() {
+                AsyncTask.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        TaskStackBuilder.create(mContext)
+                                .addNextIntentWithParentStack(intent)
+                                .startActivities(getActivityOptions(),
+                                        new UserHandle(UserHandle.getUserId(appUid)));
+                    }
+                });
+                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+                return true;
+            }
+        }, false /* afterKeyguardGone */);
+    }
+
+    protected void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
+        if (snoozeOption.criterion != null) {
+            mNotificationListener.snoozeNotification(sbn.getKey(), snoozeOption.criterion.getId());
+        } else {
+            GregorianCalendar snoozeUntil = new GregorianCalendar();
+            snoozeUntil.add(Calendar.MINUTE, snoozeOption.snoozeForMinutes);
+            mNotificationListener.snoozeNotification(sbn.getKey(), snoozeUntil.getTimeInMillis());
+        }
+    }
+
+    private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
+        row.inflateGuts();
+        row.setGutsView(item);
+        final StatusBarNotification sbn = row.getStatusBarNotification();
+        row.setTag(sbn.getPackageName());
+        final NotificationGuts guts = row.getGuts();
+        guts.setClosedListener((NotificationGuts g) -> {
+            if (!row.isRemoved()) {
+                mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
+            }
+            mNotificationGutsExposed = null;
+            mGutsMenuItem = null;
+        });
+
+        if (item.gutsContent instanceof SnoozeGutsContent) {
+            ((SnoozeGutsContent) item.gutsContent).setSnoozeListener(getSnoozeListener());
+            ((SnoozeGutsContent) item.gutsContent).setStatusBarNotification(sbn);
+            ((NotificationSnooze) item.gutsContent).setSnoozeOptions(row.getEntry().snoozeCriteria);
+        }
+
+        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 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);
+        done.getLocationOnScreen(doneLocation);
+
+        final int centerX = done.getWidth() / 2;
+        final int centerY = done.getHeight() / 2;
+        final int x = doneLocation[0] - rowLocation[0] + centerX;
+        final int y = doneLocation[1] - rowLocation[1] + centerY;
+        dismissPopups(x, y);
+    }
+
+    protected SwipeHelper.LongPressListener getNotificationLongClicker() {
+        return new SwipeHelper.LongPressListener() {
+            @Override
+            public boolean onLongPress(View v, final int x, final int y,
+                    MenuItem item) {
+                if (!(v instanceof ExpandableNotificationRow)) {
+                    return false;
+                }
+                if (v.getWindowToken() == null) {
+                    Log.e(TAG, "Trying to show notification guts, but not attached to window");
+                    return false;
+                }
+
+                final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+                bindGuts(row, item);
+                NotificationGuts guts = row.getGuts();
+
+                // Assume we are a status_bar_notification_row
+                if (guts == null) {
+                    // This view has no guts. Examples are the more card or the dismiss all view
+                    return false;
+                }
+
+                // Already showing?
+                if (guts.getVisibility() == View.VISIBLE) {
+                    dismissPopups(x, y);
+                    return false;
+                }
+
+                MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_CONTROLS);
+
+                // ensure that it's laid but not visible until actually laid out
+                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 "
+                                    + "window");
+                            return;
+                        }
+                        dismissPopups(-1 /* x */, -1 /* y */, false /* resetGear */,
+                                false /* animate */);
+                        guts.setVisibility(View.VISIBLE);
+                        final double horz = Math.max(guts.getWidth() - x, x);
+                        final double vert = Math.max(guts.getHeight() - y, y);
+                        final float r = (float) Math.hypot(horz, vert);
+                        final Animator a
+                                = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
+                        a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                        a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+                        a.addListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                super.onAnimationEnd(animation);
+                                // Move the notification view back over the gear
+                                row.resetTranslation();
+                            }
+                        });
+                        a.start();
+                        guts.setExposed(true /* exposed */,
+                                mState == StatusBarState.KEYGUARD /* needsFalsingProtection */);
+                        row.closeRemoteInput();
+                        mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+                        mNotificationGutsExposed = guts;
+                        mGutsMenuItem = item;
+                    }
+                });
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Returns the exposed NotificationGuts or null if none are exposed.
+     */
+    public NotificationGuts getExposedGuts() {
+        return mNotificationGutsExposed;
+    }
+
+    public void dismissPopups() {
+        dismissPopups(-1 /* x */, -1 /* y */, true /* resetGear */, false /* animate */);
+    }
+
+    private void dismissPopups(int x, int y) {
+        dismissPopups(x, y, true /* resetGear */, false /* animate */);
+    }
+
+    public void dismissPopups(int x, int y, boolean resetGear, boolean animate) {
+        if (mNotificationGutsExposed != null) {
+            mNotificationGutsExposed.closeControls(x, y, true /* save */);
+        }
+        if (resetGear) {
+            mStackScroller.resetExposedGearView(animate, true /* force */);
+        }
+    }
+
+    @Override
+    public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) {
+        int msg = MSG_SHOW_RECENT_APPS;
+        mHandler.removeMessages(msg);
+        mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, fromHome ? 1 : 0).sendToTarget();
+    }
+
+    @Override
+    public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        int msg = MSG_HIDE_RECENT_APPS;
+        mHandler.removeMessages(msg);
+        mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0,
+                triggeredFromHomeKey ? 1 : 0).sendToTarget();
+    }
+
+    @Override
+    public void toggleRecentApps() {
+        toggleRecents();
+    }
+
+    @Override
+    public void toggleSplitScreen() {
+        toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
+    }
+
+    @Override
+    public void preloadRecentApps() {
+        int msg = MSG_PRELOAD_RECENT_APPS;
+        mHandler.removeMessages(msg);
+        mHandler.sendEmptyMessage(msg);
+    }
+
+    @Override
+    public void cancelPreloadRecentApps() {
+        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
+        mHandler.removeMessages(msg);
+        mHandler.sendEmptyMessage(msg);
+    }
+
+    @Override
+    public void dismissKeyboardShortcutsMenu() {
+        int msg = MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU;
+        mHandler.removeMessages(msg);
+        mHandler.sendEmptyMessage(msg);
+    }
+
+    @Override
+    public void toggleKeyboardShortcutsMenu(int deviceId) {
+        int msg = MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU;
+        mHandler.removeMessages(msg);
+        mHandler.obtainMessage(msg, deviceId, 0).sendToTarget();
+    }
+
+    /** Jumps to the next affiliated task in the group. */
+    public void showNextAffiliatedTask() {
+        int msg = MSG_SHOW_NEXT_AFFILIATED_TASK;
+        mHandler.removeMessages(msg);
+        mHandler.sendEmptyMessage(msg);
+    }
+
+    /** Jumps to the previous affiliated task in the group. */
+    public void showPreviousAffiliatedTask() {
+        int msg = MSG_SHOW_PREV_AFFILIATED_TASK;
+        mHandler.removeMessages(msg);
+        mHandler.sendEmptyMessage(msg);
+    }
+
+    protected void sendCloseSystemWindows(String reason) {
+        try {
+            ActivityManager.getService().closeSystemDialogs(reason);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** Proxy for RecentsComponent */
+
+    protected void showRecents(boolean triggeredFromAltTab, boolean fromHome) {
+        if (mRecents != null) {
+            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+            mRecents.showRecents(triggeredFromAltTab, fromHome);
+        }
+    }
+
+    protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        if (mRecents != null) {
+            mRecents.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+        }
+    }
+
+    protected void toggleRecents() {
+        if (mRecents != null) {
+            mRecents.toggleRecents(mDisplay);
+        }
+    }
+
+    protected void preloadRecents() {
+        if (mRecents != null) {
+            mRecents.preloadRecents();
+        }
+    }
+
+    protected void toggleKeyboardShortcuts(int deviceId) {
+        KeyboardShortcuts.toggle(mContext, deviceId);
+    }
+
+    protected void dismissKeyboardShortcuts() {
+        KeyboardShortcuts.dismiss();
+    }
+
+    protected void cancelPreloadingRecents() {
+        if (mRecents != null) {
+            mRecents.cancelPreloadingRecents();
+        }
+    }
+
+    protected void showRecentsNextAffiliatedTask() {
+        if (mRecents != null) {
+            mRecents.showNextAffiliatedTask();
+        }
+    }
+
+    protected void showRecentsPreviousAffiliatedTask() {
+        if (mRecents != null) {
+            mRecents.showPrevAffiliatedTask();
+        }
+    }
+
+    /**
+     * Save the current "public" (locked and secure) state of the lockscreen.
+     */
+    public void setLockscreenPublicMode(boolean publicMode, int userId) {
+        mLockscreenPublicMode.put(userId, publicMode);
+    }
+
+    public boolean isLockscreenPublicMode(int userId) {
+        return mLockscreenPublicMode.get(userId, false);
+    }
+
+    /**
+     * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
+     * "public" (secure & locked) mode?
+     */
+    public boolean userAllowsNotificationsInPublic(int userHandle) {
+        if (userHandle == UserHandle.USER_ALL) {
+            return true;
+        }
+
+        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+            final boolean allowed = 0 != Settings.Secure.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+            mUsersAllowingNotifications.append(userHandle, allowed);
+            return allowed;
+        }
+
+        return mUsersAllowingNotifications.get(userHandle);
+    }
+
+    /**
+     * Has the given user chosen to allow their private (full) notifications to be shown even
+     * when the lockscreen is in "public" (secure & locked) mode?
+     */
+    public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
+        if (userHandle == UserHandle.USER_ALL) {
+            return true;
+        }
+
+        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+            final boolean allowedByUser = 0 != Settings.Secure.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+            final boolean allowedByDpm = adminAllowsUnredactedNotifications(userHandle);
+            final boolean allowed = allowedByUser && allowedByDpm;
+            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+            return allowed;
+        }
+
+        return mUsersAllowingPrivateNotifications.get(userHandle);
+    }
+
+    private boolean adminAllowsUnredactedNotifications(int userHandle) {
+        if (userHandle == UserHandle.USER_ALL) {
+            return true;
+        }
+        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
+                    userHandle);
+        return (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
+    }
+
+    /**
+     * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
+     * If so, notifications should be hidden.
+     */
+    @Override  // NotificationData.Environment
+    public boolean shouldHideNotifications(int userId) {
+        return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
+                || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
+    }
+
+    /**
+     * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
+     * package-specific override.
+     */
+    @Override // NotificationDate.Environment
+    public boolean shouldHideNotifications(String key) {
+        return isLockscreenPublicMode(mCurrentUserId)
+                && mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET;
+    }
+
+    /**
+     * Returns true if we're on a secure lockscreen.
+     */
+    @Override  // NotificationData.Environment
+    public boolean isSecurelyLocked(int userId) {
+        return isLockscreenPublicMode(userId);
+    }
+
+    public void onNotificationClear(StatusBarNotification notification) {
+        try {
+            mBarService.onNotificationClear(
+                    notification.getPackageName(),
+                    notification.getTag(),
+                    notification.getId(),
+                    notification.getUserId());
+        } catch (android.os.RemoteException ex) {
+            // oh well
+        }
+    }
+
+    /**
+     * Called when the notification panel layouts
+     */
+    public void onPanelLaidOut() {
+        if (mState == StatusBarState.KEYGUARD) {
+            // Since the number of notifications is determined based on the height of the view, we
+            // need to update them.
+            int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
+            int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+            if (maxBefore != maxNotifications) {
+                updateRowStates();
+            }
+        }
+    }
+
+    protected boolean inflateViews(Entry entry, ViewGroup parent) {
+        PackageManager pmUser = getPackageManagerForUser(mContext,
+                entry.notification.getUser().getIdentifier());
+
+        final StatusBarNotification sbn = entry.notification;
+        boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
+        try {
+            entry.cacheContentViews(mContext, null, isLowPriority);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Unable to get notification remote views", e);
+            return false;
+        }
+
+        final RemoteViews contentView = entry.cachedContentView;
+        final RemoteViews bigContentView = entry.cachedBigContentView;
+        final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+        final RemoteViews publicContentView = entry.cachedPublicContentView;
+        final RemoteViews ambientContentView = entry.cachedAmbientContentView;
+
+        if (contentView == null) {
+            Log.v(TAG, "no contentView for: " + sbn.getNotification());
+            return false;
+        }
+
+        if (DEBUG) {
+            Log.v(TAG, "publicContentView: " + publicContentView);
+        }
+
+        ExpandableNotificationRow row;
+
+        // Stash away previous user expansion state so we can restore it at
+        // the end.
+        boolean hasUserChangedExpansion = false;
+        boolean userExpanded = false;
+        boolean userLocked = false;
+
+        if (entry.row != null) {
+            row = entry.row;
+            hasUserChangedExpansion = row.hasUserChangedExpansion();
+            userExpanded = row.isUserExpanded();
+            userLocked = row.isUserLocked();
+            entry.reset();
+            if (hasUserChangedExpansion) {
+                row.setUserExpanded(userExpanded);
+            }
+        } else {
+            // create the row view
+            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
+                    parent, false);
+            row.setExpansionLogger(this, entry.notification.getKey());
+            row.setGroupManager(mGroupManager);
+            row.setHeadsUpManager(mHeadsUpManager);
+            row.setRemoteInputController(mRemoteInputController);
+            row.setOnExpandClickListener(this);
+
+            // Get the app name.
+            // Note that Notification.Builder#bindHeaderAppName has similar logic
+            // but since this field is used in the guts, it must be accurate.
+            // Therefore we will only show the application label, or, failing that, the
+            // package name. No substitutions.
+            final String pkg = sbn.getPackageName();
+            String appname = pkg;
+            try {
+                final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+                        PackageManager.MATCH_UNINSTALLED_PACKAGES
+                                | PackageManager.MATCH_DISABLED_COMPONENTS);
+                if (info != null) {
+                    appname = String.valueOf(pmUser.getApplicationLabel(info));
+                }
+            } catch (NameNotFoundException e) {
+                // Do nothing
+            }
+            row.setAppName(appname);
+        }
+
+        bindDismissRunnable(row);
+        row.setIsLowPriority(isLowPriority);
+
+        // NB: the large icon is now handled entirely by the template
+
+        // bind the click event to the content area
+        NotificationContentView contentContainer = row.getPrivateLayout();
+        NotificationContentView contentContainerPublic = row.getPublicLayout();
+
+        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        if (ENABLE_REMOTE_INPUT) {
+            row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+        }
+
+        mNotificationClicker.register(row, sbn);
+
+        // set up the adaptive layout
+        View contentViewLocal = null;
+        View bigContentViewLocal = null;
+        View headsUpContentViewLocal = null;
+        View publicViewLocal = null;
+        View ambientViewLocal = null;
+        try {
+            contentViewLocal = contentView.apply(
+                    sbn.getPackageContext(mContext),
+                    contentContainer,
+                    mOnClickHandler);
+            if (bigContentView != null) {
+                bigContentViewLocal = bigContentView.apply(
+                        sbn.getPackageContext(mContext),
+                        contentContainer,
+                        mOnClickHandler);
+            }
+            if (headsUpContentView != null) {
+                headsUpContentViewLocal = headsUpContentView.apply(
+                        sbn.getPackageContext(mContext),
+                        contentContainer,
+                        mOnClickHandler);
+            }
+            if (publicContentView != null) {
+                publicViewLocal = publicContentView.apply(
+                        sbn.getPackageContext(mContext),
+                        contentContainerPublic, mOnClickHandler);
+            }
+            if (ambientContentView != null) {
+                ambientViewLocal = ambientContentView.apply(
+                        sbn.getPackageContext(mContext),
+                        contentContainer, mOnClickHandler);
+            }
+
+            if (contentViewLocal != null) {
+                contentViewLocal.setIsRootNamespace(true);
+                contentContainer.setContractedChild(contentViewLocal);
+            }
+            if (bigContentViewLocal != null) {
+                bigContentViewLocal.setIsRootNamespace(true);
+                contentContainer.setExpandedChild(bigContentViewLocal);
+            }
+            if (headsUpContentViewLocal != null) {
+                headsUpContentViewLocal.setIsRootNamespace(true);
+                contentContainer.setHeadsUpChild(headsUpContentViewLocal);
+            }
+            if (publicViewLocal != null) {
+                publicViewLocal.setIsRootNamespace(true);
+                contentContainerPublic.setContractedChild(publicViewLocal);
+            }
+
+            if (ambientViewLocal != null) {
+                ambientViewLocal.setIsRootNamespace(true);
+                contentContainer.setAmbientChild(ambientViewLocal);
+            }
+        }
+        catch (RuntimeException e) {
+            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
+            Log.e(TAG, "couldn't inflate view for notification " + ident, e);
+            return false;
+        }
+
+        // Extract target SDK version.
+        try {
+            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+            entry.targetSdk = info.targetSdkVersion;
+        } catch (NameNotFoundException ex) {
+            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+        }
+        entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
+
+        entry.row = row;
+        entry.row.setOnActivatedListener(this);
+        entry.row.setExpandable(bigContentViewLocal != null);
+
+        applyColorsAndBackgrounds(sbn, entry);
+
+        // Restore previous flags.
+        if (hasUserChangedExpansion) {
+            // Note: setUserExpanded() conveniently ignores calls with
+            //       userExpanded=true if !isExpandable().
+            row.setUserExpanded(userExpanded);
+        }
+        row.setUserLocked(userLocked);
+        row.onNotificationUpdated(entry);
+        return true;
+    }
+
+    /**
+     * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
+     * via first-class API.
+     *
+     * TODO: Remove once enough apps specify remote inputs on their own.
+     */
+    private void processForRemoteInput(Notification n) {
+        if (!ENABLE_REMOTE_INPUT) return;
+
+        if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
+                (n.actions == null || n.actions.length == 0)) {
+            Notification.Action viableAction = null;
+            Notification.WearableExtender we = new Notification.WearableExtender(n);
+
+            List<Notification.Action> actions = we.getActions();
+            final int numActions = actions.size();
+
+            for (int i = 0; i < numActions; i++) {
+                Notification.Action action = actions.get(i);
+                if (action == null) {
+                    continue;
+                }
+                RemoteInput[] remoteInputs = action.getRemoteInputs();
+                if (remoteInputs == null) {
+                    continue;
+                }
+                for (RemoteInput ri : remoteInputs) {
+                    if (ri.getAllowFreeFormInput()) {
+                        viableAction = action;
+                        break;
+                    }
+                }
+                if (viableAction != null) {
+                    break;
+                }
+            }
+
+            if (viableAction != null) {
+                Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
+                rebuilder.setActions(viableAction);
+                rebuilder.build(); // will rewrite n
+            }
+        }
+    }
+
+    public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
+        if (!isDeviceProvisioned()) return;
+
+        final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+        final boolean afterKeyguardGone = intent.isActivity()
+                && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+                mCurrentUserId);
+        dismissKeyguardThenExecute(new OnDismissAction() {
+            @Override
+            public boolean onDismiss() {
+                new Thread() {
+                    @Override
+                    public void run() {
+                        try {
+                            // The intent we are sending is for the application, which
+                            // won't have permission to immediately start an activity after
+                            // the user switches to home.  We know it is safe to do at this
+                            // point, so make sure new activity switches are now allowed.
+                            ActivityManager.getService().resumeAppSwitches();
+                        } catch (RemoteException e) {
+                        }
+                        try {
+                            intent.send(null, 0, null, null, null, null, getActivityOptions());
+                        } catch (PendingIntent.CanceledException e) {
+                            // the stack trace isn't very helpful here.
+                            // Just log the exception message.
+                            Log.w(TAG, "Sending intent failed: " + e);
+
+                            // TODO: Dismiss Keyguard.
+                        }
+                        if (intent.isActivity()) {
+                            mAssistManager.hideAssist();
+                        }
+                    }
+                }.start();
+
+                // close the shade if it was open
+                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                        true /* force */, true /* delayed */);
+                visibilityChanged(false);
+
+                return true;
+            }
+        }, afterKeyguardGone);
+    }
+
+
+    private final class NotificationClicker implements View.OnClickListener {
+
+        @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.");
+                return;
+            }
+
+            wakeUpIfDozing(SystemClock.uptimeMillis(), v);
+
+            final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+            final StatusBarNotification sbn = row.getStatusBarNotification();
+            if (sbn == null) {
+                Log.e(TAG, "NotificationClicker called on an unclickable notification,");
+                return;
+            }
+
+            // Check if the notification is displaying the gear, if so slide notification back
+            if (row.getSettingsRow() != null && row.getSettingsRow().isVisible()) {
+                row.animateTranslateNotification(0);
+                return;
+            }
+
+            Notification notification = sbn.getNotification();
+            final PendingIntent intent = notification.contentIntent != null
+                    ? notification.contentIntent
+                    : notification.fullScreenIntent;
+            final String notificationKey = sbn.getKey();
+
+            // Mark notification for one frame.
+            row.setJustClicked(true);
+            DejankUtils.postAfterTraversal(new Runnable() {
+                @Override
+                public void run() {
+                    row.setJustClicked(false);
+                }
+            });
+
+            final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+            final boolean afterKeyguardGone = intent.isActivity()
+                    && 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.
+
+                        if (isPanelFullyCollapsed()) {
+                            HeadsUpManager.setIsClickedNotification(row, true);
+                        }
+                        //
+                        // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+                        // become canceled shortly by NoMan, but we can't assume that.
+                        mHeadsUpManager.releaseImmediately(notificationKey);
+                    }
+                    StatusBarNotification parentToCancel = null;
+                    if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+                        StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn)
+                                        .getStatusBarNotification();
+                        if (shouldAutoCancel(summarySbn)) {
+                            parentToCancel = summarySbn;
+                        }
+                    }
+                    final StatusBarNotification parentToCancelFinal = parentToCancel;
+                    new Thread() {
+                        @Override
+                        public void run() {
+                            try {
+                                // The intent we are sending is for the application, which
+                                // won't have permission to immediately start an activity after
+                                // the user switches to home.  We know it is safe to do at this
+                                // point, so make sure new activity switches are now allowed.
+                                ActivityManager.getService().resumeAppSwitches();
+                            } catch (RemoteException e) {
+                            }
+                            if (intent != null) {
+                                // If we are launching a work activity and require to launch
+                                // separate work challenge, we defer the activity action and cancel
+                                // notification until work challenge is unlocked.
+                                if (intent.isActivity()) {
+                                    final int userId = intent.getCreatorUserHandle()
+                                            .getIdentifier();
+                                    if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+                                            && mKeyguardManager.isDeviceLocked(userId)) {
+                                        boolean canBypass = false;
+                                        try {
+                                            canBypass = ActivityManager.getService()
+                                                    .canBypassWorkChallenge(intent);
+                                        } catch (RemoteException e) {
+                                        }
+                                        // For direct-boot aware activities, they can be shown when
+                                        // the device is still locked without triggering the work
+                                        // challenge.
+                                        if ((!canBypass) && startWorkChallengeIfNecessary(userId,
+                                                    intent.getIntentSender(), notificationKey)) {
+                                            // Show work challenge, do not run PendingIntent and
+                                            // remove notification
+                                            return;
+                                        }
+                                    }
+                                }
+                                try {
+                                    intent.send(null, 0, null, null, null, null,
+                                            getActivityOptions());
+                                } catch (PendingIntent.CanceledException e) {
+                                    // the stack trace isn't very helpful here.
+                                    // Just log the exception message.
+                                    Log.w(TAG, "Sending contentIntent failed: " + e);
+
+                                    // TODO: Dismiss Keyguard.
+                                }
+                                if (intent.isActivity()) {
+                                    mAssistManager.hideAssist();
+                                }
+                            }
+
+                            try {
+                                mBarService.onNotificationClick(notificationKey);
+                            } catch (RemoteException ex) {
+                                // system process is dead if we're here.
+                            }
+                            if (parentToCancelFinal != null) {
+                                // We have to post it to the UI thread for synchronization
+                                mHandler.post(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        Runnable removeRunnable = new Runnable() {
+                                            @Override
+                                            public void run() {
+                                                performRemoveNotification(parentToCancelFinal);
+                                            }
+                                        };
+                                        if (isCollapsing()) {
+                                            // To avoid lags we're only performing the remove
+                                            // after the shade was collapsed
+                                            addPostCollapseAction(removeRunnable);
+                                        } else {
+                                            removeRunnable.run();
+                                        }
+                                    }
+                                });
+                            }
+                        }
+                    }.start();
+
+                    // close the shade if it was open
+                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                            true /* force */, true /* delayed */);
+                    visibilityChanged(false);
+
+                    return true;
+                }
+            }, afterKeyguardGone);
+        }
+
+        private boolean shouldAutoCancel(StatusBarNotification sbn) {
+            int flags = sbn.getNotification().flags;
+            if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
+                return false;
+            }
+            if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+                return false;
+            }
+            return true;
+        }
+
+        public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+            Notification notification = sbn.getNotification();
+            if (notification.contentIntent != null || notification.fullScreenIntent != null) {
+                row.setOnClickListener(this);
+            } else {
+                row.setOnClickListener(null);
+            }
+        }
+    }
+
+    protected Bundle getActivityOptions() {
+        // Anything launched from the notification shade should always go into the
+        // fullscreen stack.
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+        return options.toBundle();
+    }
+
+    protected void visibilityChanged(boolean visible) {
+        if (mVisible != visible) {
+            mVisible = visible;
+            if (!visible) {
+                dismissPopups();
+            }
+        }
+        updateVisibleToUser();
+    }
+
+    protected void updateVisibleToUser() {
+        boolean oldVisibleToUser = mVisibleToUser;
+        mVisibleToUser = mVisible && mDeviceInteractive;
+
+        if (oldVisibleToUser != mVisibleToUser) {
+            handleVisibleToUserChanged(mVisibleToUser);
+        }
+    }
+
+    /**
+     * Clear Buzz/Beep/Blink.
+     */
+    public void clearNotificationEffects() {
+        try {
+            mBarService.clearNotificationEffects();
+        } catch (RemoteException e) {
+            // Won't fail unless the world has ended.
+        }
+    }
+
+    /**
+     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
+     * about the failure.
+     *
+     * WARNING: this will call back into us.  Don't hold any locks.
+     */
+    void handleNotificationError(StatusBarNotification n, String message) {
+        removeNotification(n.getKey(), null);
+        try {
+            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
+                    n.getInitialPid(), message, n.getUserId());
+        } catch (RemoteException ex) {
+            // The end is nigh.
+        }
+    }
+
+    protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
+        NotificationData.Entry entry = mNotificationData.remove(key, ranking);
+        if (entry == null) {
+            Log.w(TAG, "removeNotification for unknown key: " + key);
+            return null;
+        }
+        updateNotifications();
+        return entry.notification;
+    }
+
+    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
+        if (DEBUG) {
+            Log.d(TAG, "createNotificationViews(notification=" + sbn);
+        }
+        NotificationData.Entry entry = new NotificationData.Entry(sbn);
+        try {
+            entry.createIcons(mContext, sbn);
+        } catch (NotificationData.IconException exception) {
+            handleNotificationError(sbn, exception.getMessage());
+        }
+
+        // Construct the expanded view.
+        if (!inflateViews(entry, mStackScroller)) {
+            handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
+            return null;
+        }
+        return entry;
+    }
+
+    protected void addNotificationViews(Entry entry, RankingMap ranking) {
+        if (entry == null) {
+            return;
+        }
+        // Add the expanded view and icon.
+        mNotificationData.add(entry, ranking);
+        updateNotifications();
+    }
+
+    /**
+     * Updates expanded, dimmed and locked states of notification rows.
+     */
+    protected void updateRowStates() {
+        final int N = mStackScroller.getChildCount();
+
+        int visibleNotifications = 0;
+        boolean onKeyguard = mState == StatusBarState.KEYGUARD;
+        int maxNotifications = -1;
+        if (onKeyguard) {
+            maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+        }
+        mStackScroller.setMaxDisplayedNotifications(maxNotifications);
+        Stack<ExpandableNotificationRow> stack = new Stack<>();
+        for (int i = N - 1; i >= 0; i--) {
+            View child = mStackScroller.getChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            stack.push((ExpandableNotificationRow) child);
+        }
+        while(!stack.isEmpty()) {
+            ExpandableNotificationRow row = stack.pop();
+            NotificationData.Entry entry = row.getEntry();
+            boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification);
+            if (onKeyguard) {
+                row.setOnKeyguard(true);
+            } else {
+                row.setOnKeyguard(false);
+                row.setSystemExpanded(visibleNotifications == 0 && !childNotification);
+            }
+            entry.row.setShowAmbient(isDozing());
+            int userId = entry.notification.getUserId();
+            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
+                    entry.notification) && !entry.row.isRemoved();
+            boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
+            if (suppressedSummary
+                    || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications)
+                    || (onKeyguard && !showOnKeyguard)) {
+                entry.row.setVisibility(View.GONE);
+            } else {
+                boolean wasGone = entry.row.getVisibility() == View.GONE;
+                if (wasGone) {
+                    entry.row.setVisibility(View.VISIBLE);
+                }
+                if (!childNotification && !entry.row.isRemoved()) {
+                    if (wasGone) {
+                        // notify the scroller of a child addition
+                        mStackScroller.generateAddAnimation(entry.row,
+                                !showOnKeyguard /* fromMoreCard */);
+                    }
+                    visibleNotifications++;
+                }
+            }
+            if (row.isSummaryWithChildren()) {
+                List<ExpandableNotificationRow> notificationChildren =
+                        row.getNotificationChildren();
+                int size = notificationChildren.size();
+                for (int i = size - 1; i >= 0; i--) {
+                    stack.push(notificationChildren.get(i));
+                }
+            }
+        }
+
+        mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
+        mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
+        mStackScroller.changeViewPosition(mNotificationShelf, mStackScroller.getChildCount() - 3);
+    }
+
+    public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
+        return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
+    }
+
+    // extended in StatusBar
+    protected void setShowLockscreenNotifications(boolean show) {
+        mShowLockscreenNotifications = show;
+    }
+
+    protected void setLockScreenAllowRemoteInput(boolean allowLockscreenRemoteInput) {
+        mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
+    }
+
+    private void updateLockscreenNotificationSetting() {
+        final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+                1,
+                mCurrentUserId) != 0;
+        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+                null /* admin */, mCurrentUserId);
+        final boolean allowedByDpm = (dpmFlags
+                & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+
+        setShowLockscreenNotifications(show && allowedByDpm);
+
+        if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
+            final boolean remoteInput = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
+                    0,
+                    mCurrentUserId) != 0;
+            final boolean remoteInputDpm =
+                    (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
+
+            setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm);
+        } else {
+            setLockScreenAllowRemoteInput(false);
+        }
+    }
+
+    public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
+        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
+
+        final String key = notification.getKey();
+        Entry entry = mNotificationData.get(key);
+        if (entry == null) {
+            return;
+        } else {
+            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+            mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
+        }
+
+        Notification n = notification.getNotification();
+        mNotificationData.updateRanking(ranking);
+
+        boolean applyInPlace;
+        try {
+            applyInPlace = entry.cacheContentViews(mContext, notification.getNotification(),
+                    mNotificationData.isAmbient(key));
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Unable to get notification remote views", e);
+            applyInPlace = false;
+        }
+        boolean shouldPeek = shouldPeek(entry, notification);
+        boolean alertAgain = alertAgain(entry, n);
+        if (DEBUG) {
+            Log.d(TAG, "applyInPlace=" + applyInPlace
+                    + " shouldPeek=" + shouldPeek
+                    + " alertAgain=" + alertAgain);
+        }
+
+        final StatusBarNotification oldNotification = entry.notification;
+        entry.notification = notification;
+        mGroupManager.onEntryUpdated(entry, oldNotification);
+
+        boolean updateSuccessful = false;
+        try {
+            if (applyInPlace) {
+                if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
+                try {
+                    entry.updateIcons(mContext, n);
+                    updateNotificationViews(entry, notification);
+                    updateSuccessful = true;
+                } catch (RuntimeException e) {
+                    // It failed to apply cleanly.
+                    Log.w(TAG, "Couldn't reapply views for package " +
+                            notification.getPackageName(), e);
+                }
+            }
+            if (!updateSuccessful) {
+                entry.updateIcons(mContext, n);
+                if (!inflateViews(entry, mStackScroller)) {
+                    handleNotificationError(notification, "Couldn't update remote views for: "
+                            + notification);
+                }
+            }
+        } catch (NotificationData.IconException e) {
+            handleNotificationError(notification, e.getMessage());
+        }
+        updateHeadsUp(key, entry, shouldPeek, alertAgain);
+        updateNotifications();
+
+        if (!notification.isClearable()) {
+            // The user may have performed a dismiss action on the notification, since it's
+            // not clearable we should snap it back.
+            mStackScroller.snapViewIfNeeded(entry.row);
+        }
+
+        if (DEBUG) {
+            // Is this for you?
+            boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
+            Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+        }
+
+        setAreThereNotifications();
+    }
+
+    private void updateNotificationViews(Entry entry, StatusBarNotification sbn) {
+        final RemoteViews contentView = entry.cachedContentView;
+        final RemoteViews bigContentView = entry.cachedBigContentView;
+        final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView;
+        final RemoteViews publicContentView = entry.cachedPublicContentView;
+
+        // Reapply the RemoteViews
+        contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
+        if (bigContentView != null && entry.getExpandedContentView() != null) {
+            bigContentView.reapply(sbn.getPackageContext(mContext),
+                    entry.getExpandedContentView(),
+                    mOnClickHandler);
+        }
+        View headsUpChild = entry.getHeadsUpContentView();
+        if (headsUpContentView != null && headsUpChild != null) {
+            headsUpContentView.reapply(sbn.getPackageContext(mContext),
+                    headsUpChild, mOnClickHandler);
+        }
+        if (publicContentView != null && entry.getPublicContentView() != null) {
+            publicContentView.reapply(sbn.getPackageContext(mContext),
+                    entry.getPublicContentView(), mOnClickHandler);
+        }
+        // update the contentIntent
+        mNotificationClicker.register(entry.row, sbn);
+
+        entry.row.onNotificationUpdated(entry);
+        entry.row.resetHeight();
+    }
+
+    protected void updatePublicContentView(Entry entry,
+            StatusBarNotification sbn) {
+        final RemoteViews publicContentView = entry.cachedPublicContentView;
+        View inflatedView = entry.getPublicContentView();
+        if (entry.autoRedacted && publicContentView != null && inflatedView != null) {
+            final boolean disabledByPolicy =
+                    !adminAllowsUnredactedNotifications(entry.notification.getUserId());
+            String notificationHiddenText = mContext.getString(disabledByPolicy
+                    ? com.android.internal.R.string.notification_hidden_by_policy_text
+                    : com.android.internal.R.string.notification_hidden_text);
+            TextView titleView = (TextView) inflatedView.findViewById(android.R.id.title);
+            if (titleView != null
+                    && !titleView.getText().toString().equals(notificationHiddenText)) {
+                publicContentView.setTextViewText(android.R.id.title, notificationHiddenText);
+                publicContentView.reapply(sbn.getPackageContext(mContext),
+                        inflatedView, mOnClickHandler);
+                entry.row.onNotificationUpdated(entry);
+            }
+        }
+    }
+
+    protected void notifyHeadsUpScreenOff() {
+        maybeEscalateHeadsUp();
+    }
+
+    private boolean alertAgain(Entry oldEntry, Notification newNotification) {
+        return oldEntry == null || !oldEntry.hasInterrupted()
+                || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
+    }
+
+    protected boolean shouldPeek(Entry entry) {
+        return shouldPeek(entry, entry.notification);
+    }
+
+    protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
+        if (!mUseHeadsUp || isDeviceInVrMode()) {
+            return false;
+        }
+
+        if (mNotificationData.shouldFilterOut(sbn)) {
+            if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
+            return false;
+        }
+
+        boolean inUse = mPowerManager.isScreenOn();
+        try {
+            inUse = inUse && !mDreamManager.isDreaming();
+        } catch (RemoteException e) {
+            Log.d(TAG, "failed to query dream manager", e);
+        }
+
+        if (!inUse && !isDozing()) {
+            if (DEBUG) {
+                Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
+            if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+            return false;
+        }
+
+        if (entry.hasJustLaunchedFullScreenIntent()) {
+            if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
+            return false;
+        }
+
+        if (isSnoozedPackage(sbn)) {
+            if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
+            return false;
+        }
+
+        if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) {
+            if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
+            return false;
+        }
+
+        if (sbn.getNotification().fullScreenIntent != null) {
+            if (mAccessibilityManager.isTouchExplorationEnabled()) {
+                if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
+                return false;
+            } else {
+                // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
+                return !mStatusBarKeyguardViewManager.isShowing()
+                        || mStatusBarKeyguardViewManager.isOccluded();
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @return Whether the security bouncer from Keyguard is showing.
+     */
+    public boolean isBouncerShowing() {
+        return mBouncerShowing;
+    }
+
+    /**
+     * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then
+     *         return PackageManager for mContext
+     */
+    public static PackageManager getPackageManagerForUser(Context context, int userId) {
+        Context contextForUser = context;
+        // UserHandle defines special userId as negative values, e.g. USER_ALL
+        if (userId >= 0) {
+            try {
+                // Create a context for the correct user so if a package isn't installed
+                // for user 0 we can still load information about the package.
+                contextForUser =
+                        context.createPackageContextAsUser(context.getPackageName(),
+                        Context.CONTEXT_RESTRICTED,
+                        new UserHandle(userId));
+            } catch (NameNotFoundException e) {
+                // Shouldn't fail to find the package name for system ui.
+            }
+        }
+        return contextForUser.getPackageManager();
+    }
+
+    @Override
+    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+        try {
+            mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+        } catch (RemoteException e) {
+            // Ignore.
+        }
+    }
+
+    public boolean isKeyguardSecure() {
+        if (mStatusBarKeyguardViewManager == null) {
+            // startKeyguard() hasn't been called yet, so we don't know.
+            // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this
+            // value onVisibilityChanged().
+            Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false",
+                    new Throwable());
+            return false;
+        }
+        return mStatusBarKeyguardViewManager.isSecure();
+    }
+
+    @Override
+    public void showAssistDisclosure() {
+        if (mAssistManager != null) {
+            mAssistManager.showDisclosure();
+        }
+    }
+
+    @Override
+    public void startAssist(Bundle args) {
+        if (mAssistManager != null) {
+            mAssistManager.startAssist(args);
+        }
+    }
+    // End Extra BaseStatusBarMethods.
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index a0425e6..46ca3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import android.animation.ArgbEvaluator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -25,8 +24,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -64,7 +61,7 @@
     public static final int DEFAULT_ICON_TINT = Color.WHITE;
 
     private Context mContext;
-    private PhoneStatusBar mPhoneStatusBar;
+    private StatusBar mStatusBar;
     private DemoStatusIcons mDemoStatusIcons;
 
     private LinearLayout mSystemIconArea;
@@ -100,11 +97,11 @@
     private final ArraySet<String> mIconBlacklist = new ArraySet<>();
 
     public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar,
-            PhoneStatusBar phoneStatusBar) {
+            StatusBar phoneStatusBar) {
         super(context.getResources().getStringArray(
                 com.android.internal.R.array.config_statusBarIcons));
         mContext = context;
-        mPhoneStatusBar = phoneStatusBar;
+        mStatusBar = phoneStatusBar;
         mSystemIconArea = (LinearLayout) statusBar.findViewById(R.id.system_icon_area);
         mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
         mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
@@ -405,11 +402,11 @@
                 .withEndAction(null);
 
         // Synchronize the motion with the Keyguard fading if necessary.
-        if (mPhoneStatusBar.isKeyguardFadingAway()) {
+        if (mStatusBar.isKeyguardFadingAway()) {
             v.animate()
-                    .setDuration(mPhoneStatusBar.getKeyguardFadingAwayDuration())
+                    .setDuration(mStatusBar.getKeyguardFadingAwayDuration())
                     .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
-                    .setStartDelay(mPhoneStatusBar.getKeyguardFadingAwayDelay())
+                    .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
                     .start();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7e5a7da..8d5d890 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -70,7 +70,7 @@
 
     protected LockPatternUtils mLockPatternUtils;
     protected ViewMediatorCallback mViewMediatorCallback;
-    protected PhoneStatusBar mPhoneStatusBar;
+    protected StatusBar mStatusBar;
     private ScrimController mScrimController;
     private FingerprintUnlockController mFingerprintUnlockController;
 
@@ -103,12 +103,12 @@
         mLockPatternUtils = lockPatternUtils;
     }
 
-    public void registerStatusBar(PhoneStatusBar phoneStatusBar,
+    public void registerStatusBar(StatusBar statusBar,
             ViewGroup container, StatusBarWindowManager statusBarWindowManager,
             ScrimController scrimController,
             FingerprintUnlockController fingerprintUnlockController,
             DismissCallbackRegistry dismissCallbackRegistry) {
-        mPhoneStatusBar = phoneStatusBar;
+        mStatusBar = statusBar;
         mContainer = container;
         mStatusBarWindowManager = statusBarWindowManager;
         mScrimController = scrimController;
@@ -136,10 +136,10 @@
         if (mBouncer.needsFullscreenBouncer()) {
 
             // The keyguard might be showing (already). So we need to hide it.
-            mPhoneStatusBar.hideKeyguard();
+            mStatusBar.hideKeyguard();
             mBouncer.show(true /* resetSecuritySelection */);
         } else {
-            mPhoneStatusBar.showKeyguard();
+            mStatusBar.showKeyguard();
             if (hideBouncerWhenShowing) {
                 mBouncer.hide(false /* destroyView */);
                 mBouncer.prepare();
@@ -180,8 +180,8 @@
     public void reset(boolean hideBouncerWhenShowing) {
         if (mShowing) {
             if (mOccluded) {
-                mPhoneStatusBar.hideKeyguard();
-                mPhoneStatusBar.stopWaitingForKeyguardExit();
+                mStatusBar.hideKeyguard();
+                mStatusBar.stopWaitingForKeyguardExit();
                 mBouncer.hide(false /* destroyView */);
             } else {
                 showBouncerOrKeyguard(hideBouncerWhenShowing);
@@ -192,12 +192,12 @@
     }
 
     public void onStartedGoingToSleep() {
-        mPhoneStatusBar.onStartedGoingToSleep();
+        mStatusBar.onStartedGoingToSleep();
     }
 
     public void onFinishedGoingToSleep() {
         mDeviceInteractive = false;
-        mPhoneStatusBar.onFinishedGoingToSleep();
+        mStatusBar.onFinishedGoingToSleep();
         mBouncer.onScreenTurnedOff();
     }
 
@@ -205,13 +205,13 @@
         Trace.beginSection("StatusBarKeyguardViewManager#onStartedWakingUp");
         mDeviceInteractive = true;
         mDeviceWillWakeUp = false;
-        mPhoneStatusBar.onStartedWakingUp();
+        mStatusBar.onStartedWakingUp();
         Trace.endSection();
     }
 
     public void onScreenTurningOn() {
         Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurningOn");
-        mPhoneStatusBar.onScreenTurningOn();
+        mStatusBar.onScreenTurningOn();
         Trace.endSection();
     }
 
@@ -228,7 +228,7 @@
                     true /* skipFirstFrame */);
             updateStates();
         }
-        mPhoneStatusBar.onScreenTurnedOn();
+        mStatusBar.onScreenTurnedOn();
         Trace.endSection();
     }
 
@@ -240,7 +240,7 @@
 
     public void onScreenTurnedOff() {
         mScreenTurnedOn = false;
-        mPhoneStatusBar.onScreenTurnedOff();
+        mStatusBar.onScreenTurnedOff();
     }
 
     public void notifyDeviceWakeUpRequested() {
@@ -257,12 +257,12 @@
 
     public void setOccluded(boolean occluded, boolean animate) {
         if (occluded != mOccluded) {
-            mPhoneStatusBar.onKeyguardOccludedChanged(occluded);
+            mStatusBar.onKeyguardOccludedChanged(occluded);
         }
         if (occluded && !mOccluded && mShowing) {
-            if (mPhoneStatusBar.isInLaunchTransition()) {
+            if (mStatusBar.isInLaunchTransition()) {
                 mOccluded = true;
-                mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */,
+                mStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */,
                         new Runnable() {
                             @Override
                             public void run() {
@@ -275,7 +275,7 @@
         }
         mOccluded = occluded;
         if (mShowing) {
-            mPhoneStatusBar.updateMediaMetaData(false, animate && !occluded);
+            mStatusBar.updateMediaMetaData(false, animate && !occluded);
         }
         mStatusBarWindowManager.setKeyguardOccluded(occluded);
 
@@ -283,7 +283,7 @@
         // a FLAG_DISMISS_KEYGUARD_ACTIVITY.
         reset(false /* hideBouncerWhenShowing*/);
         if (animate && !occluded && mShowing) {
-            mPhoneStatusBar.animateKeyguardUnoccluding();
+            mStatusBar.animateKeyguardUnoccluding();
         }
     }
 
@@ -318,8 +318,8 @@
         long uptimeMillis = SystemClock.uptimeMillis();
         long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
 
-        if (mPhoneStatusBar.isInLaunchTransition() ) {
-            mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
+        if (mStatusBar.isInLaunchTransition() ) {
+            mStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
                 @Override
                 public void run() {
                     mStatusBarWindowManager.setKeyguardShowing(false);
@@ -327,14 +327,14 @@
                     mBouncer.hide(true /* destroyView */);
                     updateStates();
                     mScrimController.animateKeyguardFadingOut(
-                            PhoneStatusBar.FADE_KEYGUARD_START_DELAY,
-                            PhoneStatusBar.FADE_KEYGUARD_DURATION, null,
+                            StatusBar.FADE_KEYGUARD_START_DELAY,
+                            StatusBar.FADE_KEYGUARD_DURATION, null,
                             false /* skipFirstFrame */);
                 }
             }, new Runnable() {
                 @Override
                 public void run() {
-                    mPhoneStatusBar.hideKeyguard();
+                    mStatusBar.hideKeyguard();
                     mStatusBarWindowManager.setKeyguardFadingAway(false);
                     mViewMediatorCallback.keyguardGone();
                     executeAfterKeyguardGoneAction();
@@ -348,19 +348,19 @@
                 delay = 0;
                 fadeoutDuration = 240;
             }
-            mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
+            mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
             mFingerprintUnlockController.startKeyguardFadingAway();
             mBouncer.hide(true /* destroyView */);
             updateStates();
             if (wakeUnlockPulsing) {
                 mStatusBarWindowManager.setKeyguardFadingAway(true);
-                mPhoneStatusBar.fadeKeyguardWhilePulsing();
+                mStatusBar.fadeKeyguardWhilePulsing();
                 animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
-                        mPhoneStatusBar::hideKeyguard, false /* skipFirstFrame */);
+                        mStatusBar::hideKeyguard, false /* skipFirstFrame */);
             } else {
                 mFingerprintUnlockController.startKeyguardFadingAway();
-                mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
-                boolean staying = mPhoneStatusBar.hideKeyguard();
+                mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
+                boolean staying = mStatusBar.hideKeyguard();
                 if (!staying) {
                     mStatusBarWindowManager.setKeyguardFadingAway(true);
                     if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
@@ -379,7 +379,7 @@
                     }
                 } else {
                     mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
-                    mPhoneStatusBar.finishKeyguardFadingAway();
+                    mStatusBar.finishKeyguardFadingAway();
                 }
             }
             mStatusBarWindowManager.setKeyguardShowing(false);
@@ -408,7 +408,7 @@
                 }
                 mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
                         100);
-                mPhoneStatusBar.finishKeyguardFadingAway();
+                mStatusBar.finishKeyguardFadingAway();
                 mFingerprintUnlockController.finishKeyguardFadingAway();
                 WindowManagerGlobal.getInstance().trimMemory(
                         ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
@@ -438,7 +438,7 @@
      * Dismisses the keyguard by going to the next screen or making it gone.
      */
     public void dismissAndCollapse() {
-        mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, true, false, true);
+        mStatusBar.executeRunnableDismissingKeyguard(null, null, true, false, true);
     }
 
     public void dismiss() {
@@ -466,7 +466,7 @@
      */
     public boolean onBackPressed() {
         if (mBouncer.isShowing()) {
-            mPhoneStatusBar.endAffordanceLaunch();
+            mStatusBar.endAffordanceLaunch();
             reset(true /* hideBouncerWhenShowing */);
             return true;
         }
@@ -478,8 +478,8 @@
     }
 
     private long getNavBarShowDelay() {
-        if (mPhoneStatusBar.isKeyguardFadingAway()) {
-            return mPhoneStatusBar.getKeyguardFadingAwayDelay();
+        if (mStatusBar.isKeyguardFadingAway()) {
+            return mStatusBar.getKeyguardFadingAwayDelay();
         } else {
 
             // Keyguard is not going away, thus we are showing the navigation bar because the
@@ -491,7 +491,7 @@
     private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() {
         @Override
         public void run() {
-            mPhoneStatusBar.getNavigationBarView().getRootView().setVisibility(View.VISIBLE);
+            mStatusBar.getNavigationBarView().getRootView().setVisibility(View.VISIBLE);
         }
     };
 
@@ -516,7 +516,7 @@
         boolean navBarVisible = isNavBarVisible();
         boolean lastNavBarVisible = getLastNavBarVisible();
         if (navBarVisible != lastNavBarVisible || mFirstUpdate) {
-            if (mPhoneStatusBar.getNavigationBarView() != null) {
+            if (mStatusBar.getNavigationBarView() != null) {
                 if (navBarVisible) {
                     long delay = getNavBarShowDelay();
                     if (delay == 0) {
@@ -527,14 +527,14 @@
                     }
                 } else {
                     mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
-                    mPhoneStatusBar.getNavigationBarView().getRootView().setVisibility(View.GONE);
+                    mStatusBar.getNavigationBarView().getRootView().setVisibility(View.GONE);
                 }
             }
         }
 
         if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
             mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
-            mPhoneStatusBar.setBouncerShowing(bouncerShowing);
+            mStatusBar.setBouncerShowing(bouncerShowing);
             mScrimController.setBouncerShowing(bouncerShowing);
         }
 
@@ -553,7 +553,7 @@
         mLastBouncerDismissible = bouncerDismissible;
         mLastRemoteInputActive = remoteInputActive;
 
-        mPhoneStatusBar.onKeyguardViewManagerStatesUpdated();
+        mStatusBar.onKeyguardViewManagerStatesUpdated();
     }
 
     /**
@@ -583,11 +583,11 @@
     }
 
     public boolean shouldDisableWindowAnimationsForUnlock() {
-        return mPhoneStatusBar.isInLaunchTransition();
+        return mStatusBar.isInLaunchTransition();
     }
 
     public boolean isGoingToNotificationShade() {
-        return mPhoneStatusBar.isGoingToNotificationShade();
+        return mStatusBar.isGoingToNotificationShade();
     }
 
     public boolean isSecure(int userId) {
@@ -595,11 +595,11 @@
     }
 
     public void keyguardGoingAway() {
-        mPhoneStatusBar.keyguardGoingAway();
+        mStatusBar.keyguardGoingAway();
     }
 
     public void animateCollapsePanels(float speedUpFactor) {
-        mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
+        mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
                 false /* delayed */, speedUpFactor);
     }
 
@@ -616,6 +616,6 @@
     }
 
     public ViewRootImpl getViewRootImpl() {
-        return mPhoneStatusBar.getStatusBarView().getViewRootImpl();
+        return mStatusBar.getStatusBarView().getViewRootImpl();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 0660054..16999b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -25,7 +25,6 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.os.Trace;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
@@ -34,7 +33,6 @@
 
 import com.android.keyguard.R;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 
@@ -140,7 +138,7 @@
     private void applyFocusableFlag(State state) {
         boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
         if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
-                || BaseStatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
+                || StatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
@@ -385,7 +383,7 @@
         boolean backdropShowing;
 
         /**
-         * The {@link BaseStatusBar} state from the status bar.
+         * The {@link StatusBar} state from the status bar.
          */
         int statusBarState;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index ccd6357..7c42d00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -34,7 +34,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.ActionMode;
 import android.view.InputQueue;
@@ -56,7 +55,6 @@
 import com.android.internal.widget.FloatingToolbar;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -64,7 +62,7 @@
 
 public class StatusBarWindowView extends FrameLayout {
     public static final String TAG = "StatusBarWindowView";
-    public static final boolean DEBUG = BaseStatusBar.DEBUG;
+    public static final boolean DEBUG = StatusBar.DEBUG;
 
     private DragDownHelper mDragDownHelper;
     private NotificationStackScrollLayout mStackScrollLayout;
@@ -74,7 +72,7 @@
     private int mRightInset = 0;
     private int mLeftInset = 0;
 
-    private PhoneStatusBar mService;
+    private StatusBar mService;
     private final Paint mTransparentSrcPaint = new Paint();
     private FalsingManager mFalsingManager;
 
@@ -173,7 +171,7 @@
         }
     }
 
-    public void setService(PhoneStatusBar service) {
+    public void setService(StatusBar service) {
         mService = service;
         mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index a8d8f60..8c805fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -37,7 +37,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -90,7 +90,7 @@
     private final int mStatusBarHeight;
     private final Context mContext;
     private final NotificationGroupManager mGroupManager;
-    private PhoneStatusBar mBar;
+    private StatusBar mBar;
     private int mSnoozeLengthMs;
     private ContentObserver mSettingsObserver;
     private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
@@ -162,7 +162,7 @@
         mIsObserving = shouldObserve;
     }
 
-    public void setBar(PhoneStatusBar bar) {
+    public void setBar(StatusBar bar) {
         mBar = bar;
     }
 
@@ -170,7 +170,7 @@
         mListeners.add(listener);
     }
 
-    public PhoneStatusBar getBar() {
+    public StatusBar getBar() {
         return mBar;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 42c20ff..886b8be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -56,7 +56,7 @@
     private final WifiNetworkScoreCache mScoreCache;
     private final WifiStatusTracker mWifiTracker;
 
-    private boolean mScoringEnabled = false;
+    private boolean mScoringUiEnabled = false;
 
     public WifiSignalController(Context context, boolean hasMobileData,
             CallbackHandler callbackHandler, NetworkControllerImpl networkController,
@@ -95,25 +95,26 @@
 
         // Setup scoring
         mNetworkScoreManager = networkScoreManager;
+        configureScoringGating();
+        registerScoreCache();
+    }
+
+    private void configureScoringGating() {
         ContentObserver observer = new ContentObserver(new Handler(Looper.getMainLooper())) {
             @Override
             public void onChange(boolean selfChange) {
-                mScoringEnabled =
+                mScoringUiEnabled =
                         Settings.Global.getInt(
                                 mContext.getContentResolver(),
-                                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1;
-                if (!mScoringEnabled) {
-                    mScoreCache.clearScores();
-                }
+                                Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
             }
         };
-        ContentResolver cr = mContext.getContentResolver();
-        cr.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED),
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED),
                 false /* notifyForDescendants */,
                 observer);
-        observer.onChange(false /* selfChange */);
-        registerScoreCache();
+
+        observer.onChange(false /* selfChange */); // Set the initial values
     }
 
     private void registerScoreCache() {
@@ -199,7 +200,7 @@
      * <p>{@link #updateScoreCacheIfNecessary} should be called prior to this method.
      */
     private int getWifiBadgeEnum() {
-        if (!mScoringEnabled || mWifiTracker.networkKey == null) {
+        if (!mScoringUiEnabled || mWifiTracker.networkKey == null) {
             return ScoredNetwork.BADGING_NONE;
         }
         ScoredNetwork score = mScoreCache.getScoredNetwork(mWifiTracker.networkKey);
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 5d48df9..85b1c32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -79,7 +79,7 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
@@ -251,7 +251,7 @@
             return true;
         }
     };
-    private PhoneStatusBar mPhoneStatusBar;
+    private StatusBar mStatusBar;
     private int[] mTempInt2 = new int[2];
     private boolean mGenerateChildOrderChangedEvent;
     private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
@@ -877,7 +877,7 @@
 
         mFalsingManager.onNotificationDismissed();
         if (mFalsingManager.shouldEnforceBouncer()) {
-            mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
+            mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
                     false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
         }
     }
@@ -968,7 +968,7 @@
 
     @Override
     public float getFalsingThresholdFactor() {
-        return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+        return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
     }
 
     @Override
@@ -3222,7 +3222,7 @@
         mAmbientState.setExpansionChanging(false);
         if (!mIsExpanded) {
             setOwnScrollY(0);
-            mPhoneStatusBar.resetUserExpandedStates();
+            mStatusBar.resetUserExpandedStates();
 
             // lets make sure nothing is in the overlay / transient anymore
             clearTemporaryViews(this);
@@ -3757,8 +3757,8 @@
         return max + getStackTranslation();
     }
 
-    public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
-        this.mPhoneStatusBar = phoneStatusBar;
+    public void setStatusBar(StatusBar statusBar) {
+        this.mStatusBar = statusBar;
     }
 
     public void setGroupManager(NotificationGroupManager groupManager) {
@@ -3829,7 +3829,7 @@
 
     @Override
     public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
-        mPhoneStatusBar.requestNotificationUpdate();
+        mStatusBar.requestNotificationUpdate();
     }
 
     /** @hide */
@@ -3897,7 +3897,7 @@
 
     @Override
     public void onGroupsChanged() {
-        mPhoneStatusBar.requestNotificationUpdate();
+        mStatusBar.requestNotificationUpdate();
     }
 
     public void generateChildOrderChangedEvent() {
@@ -4367,7 +4367,7 @@
         }
 
         public void closeControlsIfOutsideTouch(MotionEvent ev) {
-            NotificationGuts guts = mPhoneStatusBar.getExposedGuts();
+            NotificationGuts guts = mStatusBar.getExposedGuts();
             View view = null;
             int height = 0;
             if (guts != null) {
@@ -4390,7 +4390,7 @@
                 Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
                 if (!rect.contains(rx, ry)) {
                     // Touch was outside visible guts / gear notification, close what's visible
-                    mPhoneStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
+                    mStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 9b8183b..ab562d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -17,23 +17,29 @@
 package com.android.systemui.statusbar.tv;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.view.View;
 
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.statusbar.ActivatableNotificationView;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.SystemUI;
 import com.android.systemui.pip.tv.PipManager;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
+
+import java.util.ArrayList;
 
 /**
  * Status bar implementation for "large screen" products that mostly present no on-screen nav
  */
 
-public class TvStatusBar extends BaseStatusBar {
+public class TvStatusBar extends SystemUI implements Callbacks {
+
+    private IStatusBarService mBarService;
 
     @Override
     public void setIcon(String slot, StatusBarIcon icon) {
@@ -43,16 +49,6 @@
     public void removeIcon(String slot) {
     }
 
-    @Override
-    public void addNotification(StatusBarNotification notification, RankingMap ranking,
-            NotificationData.Entry entry) {
-    }
-
-    @Override
-    protected void updateNotificationRanking(RankingMap ranking) {
-    }
-
-    @Override
     public void removeNotification(String key, RankingMap ranking) {
     }
 
@@ -99,53 +95,10 @@
     }
 
     @Override
-    protected void setAreThereNotifications() {
-    }
-
-    @Override
-    protected void updateNotifications() {
-    }
-
-    public View getStatusBarView() {
-        return null;
-    }
-
-    @Override
-    protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
-        return false;
-    }
-
-    @Override
-    public void maybeEscalateHeadsUp() {
-    }
-
-    @Override
-    public boolean isPanelFullyCollapsed() {
-        return false;
-    }
-
-    @Override
-    protected int getMaxKeyguardNotifications(boolean recompute) {
-        return 0;
-    }
-
-    @Override
     public void animateExpandSettingsPanel(String subPanel) {
     }
 
     @Override
-    protected void createAndAddWindows() {
-    }
-
-    @Override
-    public void onActivated(ActivatableNotificationView view) {
-    }
-
-    @Override
-    public void onActivationReset(ActivatableNotificationView view) {
-    }
-
-    @Override
     public void showScreenPinningRequest(int taskId) {
     }
 
@@ -175,19 +128,6 @@
     }
 
     @Override
-    protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
-            boolean alertAgain) {
-    }
-
-    @Override
-    protected void setHeadsUpUser(int newUserId) {
-    }
-
-    protected boolean isSnoozedPackage(StatusBarNotification sbn) {
-        return false;
-    }
-
-    @Override
     public void addQsTile(ComponentName tile) {
     }
 
@@ -201,8 +141,23 @@
 
     @Override
     public void start() {
-        super.start();
         putComponent(TvStatusBar.class, this);
+        CommandQueue commandQueue = getComponent(CommandQueue.class);
+        commandQueue.addCallbacks(this);
+        int[] switches = new int[9];
+        ArrayList<IBinder> binders = new ArrayList<>();
+        ArrayList<String> iconSlots = new ArrayList<>();
+        ArrayList<StatusBarIcon> icons = new ArrayList<>();
+        Rect fullscreenStackBounds = new Rect();
+        Rect dockedStackBounds = new Rect();
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        try {
+            mBarService.registerStatusBar(commandQueue, iconSlots, icons, switches, binders,
+                    fullscreenStackBounds, dockedStackBounds);
+        } catch (RemoteException ex) {
+            // If the system process isn't there we're doomed anyway.
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 137a12f..78145fe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -31,7 +31,6 @@
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.qs.tiles.DndTile;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 90e9321..408e8f3 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
+    <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 525a361..e28d077 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -20,7 +20,6 @@
 import android.view.WindowManager;
 
 import com.android.systemui.FragmentTestCase;
-import com.android.systemui.assist.AssistManager;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
@@ -37,7 +36,7 @@
     @Before
     public void setup() {
         mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
-        mContext.putComponent(PhoneStatusBar.class, mock(PhoneStatusBar.class));
+        mContext.putComponent(StatusBar.class, mock(StatusBar.class));
         mContext.putComponent(Recents.class, mock(Recents.class));
         mContext.putComponent(Divider.class, mock(Divider.class));
         mContext.addMockSystemService(Context.WINDOW_SERVICE, mock(WindowManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
similarity index 80%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index d82566f..309559b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -27,7 +27,6 @@
 
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.BaseStatusBar;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -35,15 +34,15 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class PhoneStatusBarTest extends SysuiTestCase {
+public class StatusBarTest extends SysuiTestCase {
 
     StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    PhoneStatusBar mPhoneStatusBar;
+    StatusBar mStatusBar;
 
     @Before
     public void setup() {
         mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager.class);
-        mPhoneStatusBar = new TestablePhoneStatusBar(mStatusBarKeyguardViewManager);
+        mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager);
 
         doAnswer(invocation -> {
             OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
@@ -63,7 +62,7 @@
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
 
-        mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+        mStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
     }
 
     @Test
@@ -71,7 +70,7 @@
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
 
-        mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+        mStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
     }
 
     @Test
@@ -79,16 +78,16 @@
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
         when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
 
-        mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+        mStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
     }
 
-    static class TestablePhoneStatusBar extends PhoneStatusBar {
-        public TestablePhoneStatusBar(StatusBarKeyguardViewManager man) {
+    static class TestableStatusBar extends StatusBar {
+        public TestableStatusBar(StatusBarKeyguardViewManager man) {
             mStatusBarKeyguardViewManager = man;
         }
 
         @Override
-        protected BaseStatusBar.H createHandler() {
+        protected H createHandler() {
             return null;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 9110e0d..9b382f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -1,7 +1,6 @@
 package com.android.systemui.statusbar.policy;
 
 import android.content.Intent;
-import android.graphics.drawable.Drawable;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkKey;
@@ -30,7 +29,6 @@
 import org.mockito.stubbing.Answer;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
@@ -60,8 +58,6 @@
     private final List<NetworkKey> mRequestedKeys = new ArrayList<>();
     private CountDownLatch mRequestScoresLatch;
 
-    private SettingOverrider mSettingsOverrider;
-
     @Test
     public void testWifiIcon() {
         String testSsid = "Test SSID";
@@ -83,6 +79,7 @@
 
     @Test
     public void testBadgedWifiIcon() throws Exception {
+        // TODO(sghuman): Refactor this setup code when creating a test for the badged QsIcon.
         int testLevel = 1;
         RssiCurve mockBadgeCurve = mock(RssiCurve.class);
         Bundle attr = new Bundle();
@@ -94,12 +91,16 @@
                         false /* meteredHint */,
                         attr);
 
-        // Enable scoring
-        mSettingsOverrider = mContext.getSettingsProvider().acquireOverridesBuilder(this)
-                .addSetting("global", Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, "1")
-                .build();
-
+        // Must set the Settings value before instantiating the NetworkControllerImpl due to bugs in
+        // FakeSettingsProvider.
+        SettingOverrider settingsOverrider =
+                mContext.getSettingsProvider().acquireOverridesBuilder(this)
+                        .addSetting("global", Settings.Global.NETWORK_SCORING_UI_ENABLED, "1")
+                        .build();
+        super.setUp(); // re-instantiate NetworkControllImpl now that setting has been updated
         setupNetworkScoreManager();
+
+        // Test Requesting Scores
         mRequestScoresLatch = new CountDownLatch(1);
         setWifiEnabled(true);
         setWifiState(true, TEST_SSID, TEST_BSSID);
@@ -115,23 +116,23 @@
                 Matchers.anyInt());
         scoreCacheCaptor.getValue().updateScores(Arrays.asList(score));
 
+        //  Test badge is set
         setWifiLevel(testLevel);
-        NetworkController.SignalCallback mockCallback =
-                mock(NetworkController.SignalCallback.class);
-        mNetworkController.addCallback(mockCallback);
 
-        ArgumentCaptor<IconState> iconState = ArgumentCaptor.forClass(IconState.class);
-        Mockito.verify(mockCallback).setWifiIndicators(
-                anyBoolean(), iconState.capture(), any(), anyBoolean(), anyBoolean(), any());
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
+                anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(),
+                any());
+        IconState iconState = iconArg.getValue();
 
         assertEquals("Badged Wifi Resource is set",
                 Utils.WIFI_PIE_FOR_BADGING[testLevel],
-                iconState.getValue().icon);
+                iconState.icon);
         assertEquals("SD Badge is set",
                 Utils.getWifiBadgeResource(ScoredNetwork.BADGING_SD),
-                iconState.getValue().iconOverlay);
+                iconState.iconOverlay);
 
-        mSettingsOverrider.release();
+        settingsOverrider.release();
     }
 
     private void setupNetworkScoreManager() {
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 952f851..f906ee2 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3276,8 +3276,8 @@
     DEFAULT_APP_PICKER_CONFIRMATION_DIALOG = 791;
 
 
-    // OPEN: Settings > Apps > Default Apps > Default auto-fill app
-    DEFAULT_AUTO_FILL_PICKER = 792;
+    // OPEN: Settings > Apps > Default Apps > Default autofill app
+    DEFAULT_AUTOFILL_PICKER = 792;
 
     // These values should never appear in log outputs - they are reserved for
     // internal Tron use.
@@ -3346,6 +3346,11 @@
     // OS: N
     SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK = 817;
 
+    // OPEN: Settings > System > Backup
+    // CATEGORY: SETTINGS
+    // OS: O
+    BACKUP_SETTINGS = 818;
+
     // ---- End O Constants, all O constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 3523706..ce50b30 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -42,6 +42,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -1547,6 +1548,13 @@
     }
 
     @Override
+    public boolean isRequestPinAppWidgetSupported() {
+        return LocalServices.getService(ShortcutServiceInternal.class)
+                .isRequestPinItemSupported(UserHandle.getCallingUserId(),
+                        LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET);
+    }
+
+    @Override
     public boolean requestPinAppWidget(String callingPackage, ComponentName componentName,
             IntentSender resultSender) {
         final int callingUid = Binder.getCallingUid();
diff --git a/services/autofill/java/com/android/server/autofill/AnchoredWindow.java b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
new file mode 100644
index 0000000..e674309
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AnchoredWindow.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.autofill;
+
+import static com.android.server.autofill.Helper.DEBUG;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.FrameLayout;
+
+/**
+ * A window above the application that is smartly anchored to a rectangular region.
+ */
+final class AnchoredWindow {
+    private static final String TAG = "AutoFill";
+
+    private final WindowManager mWm;
+    private final View mRootView;
+    private final View mView;
+    private final int mWidth;
+    private final int mHeight;
+    private boolean mIsShowing = false;
+
+    /**
+     * Constructor.
+     *
+     * @param wm window manager that draws the view on a window
+     * @param view singleton view in the window
+     * @param width requested width of the view
+     * @param height requested height of the view
+     */
+    AnchoredWindow(WindowManager wm, View view, int width, int height) {
+        mWm = wm;
+        mRootView = wrapView(view, width, height);
+        mView = view;
+        mWidth = width;
+        mHeight = height;
+    }
+
+    /**
+     * Shows the window.
+     *
+     * @param bounds the rectangular region this window should be anchored to
+     */
+    void show(Rect bounds) {
+        LayoutParams params = createBaseLayoutParams();
+        params.x = bounds.left;
+        params.y = bounds.bottom;
+
+        if (!mIsShowing) {
+            if (DEBUG) Slog.d(TAG, "adding view " + mView);
+            mWm.addView(mRootView, params);
+        } else {
+            if (DEBUG) Slog.d(TAG, "updating view " + mView);
+            mWm.updateViewLayout(mRootView, params);
+        }
+        mIsShowing = true;
+    }
+
+    /**
+     * Hides the window.
+     */
+    void hide() {
+        if (DEBUG) Slog.d(TAG, "removing view " + mView);
+        if (mIsShowing) {
+            mWm.removeView(mRootView);
+        }
+        mIsShowing = false;
+    }
+
+    /**
+     * Wraps a view with a SelfRemovingView and sets its requested width and height.
+     */
+    private View wrapView(View view, int width, int height) {
+        ViewGroup viewGroup = new SelfRemovingView(view.getContext());
+        viewGroup.addView(view, new ViewGroup.LayoutParams(width, height));
+        return viewGroup;
+    }
+
+    private static LayoutParams createBaseLayoutParams() {
+        LayoutParams params = new LayoutParams();
+        // TODO(b/33197203): LayoutParams.TYPE_AUTOFILL
+        params.type = LayoutParams.TYPE_SYSTEM_ALERT;
+        params.flags =
+                LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+                | LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                | LayoutParams.FLAG_NOT_FOCUSABLE
+                | LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+        params.gravity = Gravity.TOP | Gravity.LEFT;
+        params.width = LayoutParams.WRAP_CONTENT;
+        params.height = LayoutParams.WRAP_CONTENT;
+        return params;
+    }
+
+    /** FrameLayout that listens for touch events removes itself if the touch event is outside. */
+    private final class SelfRemovingView extends FrameLayout {
+        public SelfRemovingView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+                hide();
+                return true;
+            } else {
+                return super.onTouchEvent(event);
+            }
+        }
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 47ac1ce..8ce5278 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -21,6 +21,7 @@
 import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
 
 import android.Manifest;
+import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -31,7 +32,6 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -39,16 +39,15 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.autofill.IAutoFillManagerService;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.TimeUtils;
 import android.view.autofill.AutoFillId;
 
 import com.android.internal.annotations.GuardedBy;
@@ -56,10 +55,12 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.server.FgThread;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
  * Entry point service for auto-fill management.
@@ -76,7 +77,8 @@
     private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
 
     protected static final int MSG_UNBIND = 1;
-    protected static final int MSG_SHOW_AUTO_FILL = 2;
+    protected static final int MSG_REQUEST_AUTO_FILL_FOR_USER = 2;
+    protected static final int MSG_REQUEST_AUTO_FILL = 3;
 
     private final AutoFillManagerServiceStub mServiceStub;
     private final AutoFillUI mUi;
@@ -91,11 +93,23 @@
         public void executeMessage(Message msg) {
             switch (msg.what) {
                 case MSG_UNBIND: {
-                    removeStaleServiceForUser(msg.arg1);
+                    synchronized (mLock) {
+                        removeCachedServiceLocked(msg.arg1);
+                    }
                     return;
-                } case MSG_SHOW_AUTO_FILL: {
+                } case MSG_REQUEST_AUTO_FILL_FOR_USER: {
+                    final int userId = msg.arg1;
+                    final int flags = msg.arg2;
+                    handleAutoFillForUser(userId, flags);
+                    return;
+                } case MSG_REQUEST_AUTO_FILL: {
                     final SomeArgs args = (SomeArgs) msg.obj;
-                    showAutoFillInput(msg.arg1, (AutoFillId) args.arg1, (Rect) args.arg2);
+                    final int userId = msg.arg1;
+                    final int flags = msg.arg2;
+                    final IBinder activityToken = (IBinder) args.arg1;
+                    final AutoFillId autoFillId = (AutoFillId) args.arg2;
+                    final Rect bounds = (Rect) args.arg3;
+                    handleAutoFill(activityToken, userId, autoFillId, bounds, flags);
                     return;
                 } default: {
                     Slog.w(TAG, "Invalid message: " + msg);
@@ -123,6 +137,9 @@
     @GuardedBy("mLock")
     private SparseArray<AutoFillManagerServiceImpl> mServicesCache = new SparseArray<>();
 
+    // TODO(b/33197203): set a different max (or disable it) on low-memory devices.
+    private final LocalLog mRequestsHistory = new LocalLog(100);
+
     public AutoFillManagerService(Context context) {
         super(context);
 
@@ -166,11 +183,11 @@
         if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
                 + serviceComponent + ", info: " + serviceInfo);
         if (serviceInfo == null) {
-            Slog.w(TAG, "no service info for " + serviceComponent);
+            if (DEBUG) Slog.d(TAG, "no service info for " + serviceComponent);
             return null;
         }
-        return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, FgThread.getHandler(),
-                userId, serviceInfo.applicationInfo.uid, serviceComponent,
+        return new AutoFillManagerServiceImpl(this, mUi, mContext, mLock, mRequestsHistory,
+                FgThread.getHandler(), userId, serviceInfo.applicationInfo.uid, serviceComponent,
                 SERVICE_BINDING_LIFETIME_MS);
     }
 
@@ -198,87 +215,92 @@
         }
         // Keep service connection alive for a while, in case user needs to interact with it
         // (for example, to save the data that was inputted in)
+        if (mHandlerCaller.hasMessages(MSG_UNBIND)) {
+            mHandlerCaller.removeMessages(MSG_UNBIND);
+        }
         mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessageI(MSG_UNBIND, userId),
                 SERVICE_BINDING_LIFETIME_MS);
         return service;
     }
 
     /**
-     * Removes a cached service, but respecting its TTL.
+     * Removes a cached service for a given user.
      */
-    private void removeStaleServiceForUser(int userId) {
-        synchronized (mLock) {
-            removeCachedService(userId, false);
-        }
-    }
-
-    /**
-     * Removes a cached service, even if it has TTL.
-     */
-    void removeCachedServiceForUserLocked(int userId) {
-        removeCachedService(userId, true);
-    }
-
-    private void removeCachedService(int userId, boolean force) {
+    void removeCachedServiceLocked(int userId) {
         if (DEBUG) Log.d(TAG, "removing cached service for userId " + userId);
         final AutoFillManagerServiceImpl service = mServicesCache.get(userId);
         if (service == null) {
-            Log.w(TAG, "removeCachedServiceForUser(): no cached service for userId " + userId);
-            return;
-        }
-        if (!force) {
-            // Check TTL first.
-            final long now = SystemClock.uptimeMillis();
-            if (service.mEstimateTimeOfDeath > now) {
-                if (DEBUG) {
-                    final StringBuilder msg = new StringBuilder("service has some TTL left: ");
-                    TimeUtils.formatDuration(service.mEstimateTimeOfDeath - now, msg);
-                    Log.d(TAG, msg.toString());
-                }
-                return;
+            if (DEBUG) {
+                Log.d(TAG, "removeCachedServiceForUser(): no cached service for userId " + userId);
             }
+            return;
         }
         mServicesCache.delete(userId);
         service.stopLocked();
-
     }
 
-
-    private void requestAutoFillLocked(IBinder activityToken, int userId, Bundle extras,
-            int flags) {
-        final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
-        if (service != null) {
-            service.requestAutoFill(activityToken, extras, flags);
+    private void handleAutoFill(IBinder activityToken, int userId, AutoFillId autoFillId,
+            Rect bounds, int flags) {
+        synchronized (mLock) {
+            final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+            if (service != null) {
+                // TODO(b/33197203): must pass AUTO_FILL_FLAG_TYPE_FILL because AM is expecting
+                // either that flag or AUTO_FILL_FLAG_TYPE_SAVE; should go away once save is
+                // refactored
+                flags |= AUTO_FILL_FLAG_TYPE_FILL;
+                service.requestAutoFillLocked(activityToken, autoFillId, bounds, flags);
+            }
         }
     }
 
-    private void showAutoFillInput(int userId, AutoFillId id, Rect rect) {
-        if (DEBUG) Slog.d(TAG, "handler.showAutoFillInput(): id=" + id + ", rect=" + rect);
-
+    private void handleAutoFillForUser(int userId, int flags) {
+        if (DEBUG) {
+            Slog.d(TAG, "handler.requestAutoFillForUser(): id=" + userId + ", flags=" + flags);
+        }
+        final List<IBinder> topActivities = LocalServices
+                .getService(ActivityManagerInternal.class).getTopVisibleActivities();
+        if (DEBUG)
+            Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
+        if (topActivities.isEmpty()) {
+            Slog.w(TAG, "Could not get top activity");
+            return;
+        }
+        final IBinder activityToken = topActivities.get(0);
         synchronized (mLock) {
-            requestAutoFillLocked(null, userId, null, AUTO_FILL_FLAG_TYPE_FILL);
+            final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+            if (service == null) {
+                Slog.w(TAG, "no service for user " + userId);
+                return;
+            }
+            service.requestAutoFillLocked(activityToken, null, null, flags);
         }
     }
 
     final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
 
         @Override
-        public void showAutoFillInput(AutoFillId id, Rect boundaries) {
-            if (DEBUG) Slog.d(TAG, "showAutoFillInput(): id=" + id + ", boundaries=" + boundaries);
+        public void requestAutoFill(AutoFillId id, Rect bounds, int flags) {
+            if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", autoFillId=" + id
+                    + ", bounds=" + bounds);
 
-            // TODO(b/33197203): fail if it's not called by same uid as the top activity
-            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW_AUTO_FILL,
-                    UserHandle.getCallingUserId(), id, boundaries));
+            // Make sure its called by the top activity.
+            final int uid = Binder.getCallingUid();
+            final IBinder activityToken = LocalServices.getService(ActivityManagerInternal.class)
+                        .getTopVisibleActivity(uid);
+            if (activityToken == null) {
+                throw new SecurityException("uid " + uid + " does not own the top activity");
+            }
+
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIIOOO(MSG_REQUEST_AUTO_FILL,
+                    UserHandle.getCallingUserId(), flags, activityToken, id, bounds));
         }
 
         @Override
-        public void requestAutoFill(IBinder activityToken, int userId, Bundle extras, int flags) {
-            if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
+        public void requestAutoFillForUser(int userId, int flags) {
             mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
 
-            synchronized (mLock) {
-                requestAutoFillLocked(activityToken, userId, extras, flags);
-            }
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageII(
+                    MSG_REQUEST_AUTO_FILL_FOR_USER, userId, flags));
         }
 
         @Override
@@ -304,6 +326,9 @@
                     }
                 }
             }
+            mUi.dump(pw);
+            pw.println("Requests history:");
+            mRequestsHistory.reverseDump(fd, pw, args);
         }
 
         @Override
@@ -326,7 +351,7 @@
         public void onChange(boolean selfChange, Uri uri, int userId) {
             if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId);
             synchronized (mLock) {
-                removeCachedServiceForUserLocked(userId);
+                removeCachedServiceLocked(userId);
             }
         }
     }
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index 77e7b31..0dd891c 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -16,16 +16,18 @@
 
 package com.android.server.autofill;
 
-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 static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
+import static android.view.autofill.AutoFillManager.FLAG_UPDATE_UI_HIDE;
+
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.bundleToString;
 
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
 import android.app.IActivityManager;
 import android.app.assist.AssistStructure;
 import android.content.BroadcastReceiver;
@@ -35,6 +37,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.graphics.Rect;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -53,6 +56,7 @@
 import android.service.autofill.IAutoFillServerCallback;
 import android.service.autofill.IAutoFillService;
 import android.service.voice.VoiceInteractionSession;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -64,9 +68,9 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
-import com.android.server.LocalServices;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
@@ -86,6 +90,7 @@
     private final int mUserId;
     private final int mUid;
     private final ComponentName mComponent;
+    private final String mComponentName;
     private final Context mContext;
     private final IActivityManager mAm;
     private final Object mLock;
@@ -93,14 +98,15 @@
     private final AutoFillManagerService mManagerService;
     private final AutoFillUI mUi;
 
-    // TODO(b/33197203): improve its usage
-    // - set maximum number of entries
-    // - disable on low-memory devices.
-    private final List<String> mRequestHistory = new LinkedList<>();
+    // Token used for fingerprint authentication
+    // TODO(b/33197203): create on demand?
+    private final IBinder mAuthToken = new Binder();
 
     @GuardedBy("mLock")
     private final List<QueuedRequest> mQueuedRequests = new LinkedList<>();
 
+    private final LocalLog mRequestsHistory;
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -140,9 +146,10 @@
                     if (DEBUG) Slog.d(TAG, "queued requests:" + mQueuedRequests.size());
                 }
                 for (final QueuedRequest request: mQueuedRequests) {
-                    requestAutoFillLocked(request.activityToken, request.extras, request.flags,
-                            false);
+                    requestAutoFillLocked(request.activityToken, request.autoFillId,
+                            request.bounds, request.flags, false);
                 }
+                mQueuedRequests.clear();
             }
         }
 
@@ -151,7 +158,7 @@
             if (DEBUG) Slog.d(TAG, name + " disconnected");
             synchronized (mLock) {
                 mService = null;
-                mManagerService.removeCachedServiceForUserLocked(mUserId);
+                mManagerService.removeCachedServiceLocked(mUserId);
             }
         }
     };
@@ -181,9 +188,9 @@
                     Slog.w(TAG, "no server callback for id " + resultCode);
                     return;
                 }
-                session.mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+                session.setAppCallback(appBinder);
             }
-            mService.autoFill(structure, session.mServerCallback, session.mExtras, flags);
+            mService.autoFill(structure, session.mServerCallback, flags);
         }
     };
 
@@ -198,15 +205,17 @@
     long mEstimateTimeOfDeath;
 
     AutoFillManagerServiceImpl(AutoFillManagerService managerService, AutoFillUI ui,
-            Context context, Object lock, Handler handler, int userId, int uid,
-            ComponentName component, long ttl) {
+            Context context, Object lock, LocalLog requestsHistory, Handler handler, int userId,
+            int uid, ComponentName component, long ttl) {
         mManagerService = managerService;
         mUi = ui;
         mContext = context;
         mLock = lock;
+        mRequestsHistory = requestsHistory;
         mUserId = userId;
         mUid = uid;
         mComponent = component;
+        mComponentName = mComponent.flattenToShortString();
         mAm = ActivityManager.getService();
         setLifeExpectancy(ttl);
 
@@ -254,60 +263,86 @@
     /**
      * Asks service to auto-fill an activity.
      *
-     * @param activityToken activity token
-     * @param extras bundle to be passed to the {@link AutoFillService} method.
+     * @param activityToken activity token.
+     * @param autoFillId id of the view that requested auto-fill.
      * @param flags optional flags.
      */
-    void requestAutoFill(@Nullable IBinder activityToken, @Nullable Bundle extras, int flags) {
-        synchronized (mLock) {
-            if (!mBound) {
-                Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
-                return;
-            }
+    void requestAutoFillLocked(IBinder activityToken, @Nullable AutoFillId autoFillId,
+            @Nullable Rect bounds, int flags) {
+        if (!mBound) {
+            Slog.w(TAG, "requestAutoFill() failed because it's not bound to service");
+            return;
         }
 
-        // TODO(b/33197203): activityToken should probably not be null, but we need to wait until
-        // the UI is triggering the call (for now it's trough 'adb shell cmd autofill request'
-        if (activityToken == null) {
-            // Let's get top activities from all visible stacks.
-
-            // TODO(b/33197203): overload getTopVisibleActivities() to take userId, otherwise it
-            // could return activities for different users when a work profile app is displayed in
-            // another window (in a multi-window environment).
-            final List<IBinder> topActivities = LocalServices
-                    .getService(ActivityManagerInternal.class).getTopVisibleActivities();
-            if (DEBUG)
-                Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
-            if (topActivities.isEmpty()) {
-                Slog.w(TAG, "Could not get top activity");
-                return;
-            }
-            activityToken = topActivities.get(0);
-        }
-
-        final String historyItem = TimeUtils.formatForLogging(System.currentTimeMillis())
-                + " - " + activityToken;
-        synchronized (mLock) {
-            mRequestHistory.add(historyItem);
-            requestAutoFillLocked(activityToken, extras, flags, true);
-        }
+        requestAutoFillLocked(activityToken, autoFillId, bounds, flags, true);
     }
 
-    private void requestAutoFillLocked(IBinder activityToken, @Nullable Bundle extras, int flags,
-            boolean queueIfNecessary) {
+    private void requestAutoFillLocked(IBinder activityToken, AutoFillId autoFillId, Rect bounds,
+            int flags, boolean queueIfNecessary) {
         if (mService == null) {
             if (!queueIfNecessary) {
                 Slog.w(TAG, "requestAutoFillLocked(): service is null");
                 return;
             }
-            if (DEBUG) Slog.d(TAG, "requestAutoFill(): service not set yet, queuing it");
-            mQueuedRequests.add(new QueuedRequest(activityToken, extras, flags));
+            if (DEBUG) Slog.d(TAG, "requestAutoFillLocked(): service not set yet, queuing it");
+            mQueuedRequests.add(new QueuedRequest(activityToken, autoFillId, bounds, flags));
+            return;
+        }
+        if (activityToken == null) {
+            // Sanity check
+            Slog.wtf(TAG, "requestAutoFillLocked(): null activityToken");
+            return;
+        }
+
+        final Session session = getSessionByTokenLocked(activityToken);
+
+        if (session != null) {
+            // Session already exist, update UI instead...
+            /*
+             * TODO(b/33197203): currently, it's always reusing the session, regardless of the
+             * requested autoFillId, but it should start a new session for views that
+             * were not part of the initial auto-fill dataset returned by the service. For example:
+             *
+             * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
+             * 2.User taps `first_name`.
+             * 3.Service returns a dataset with ids for `first_name` and `last_name`.
+             * 4.When user taps `first_name` (again) or `last_name`, session should be reused, but
+             * when user taps `address`, it should start a new session (since that field was
+             *   not part of the initial dataset).
+             *
+             * Similarly, once the activity is auto-filled, the flag logic should be reset (so if
+             * the user taps the view again, a new auto-fill request is made)
+             */
+            if (DEBUG) {
+                Slog.d(TAG, "requestAutoFillLocked(): reusing session for token "
+                        + activityToken + ", id " + autoFillId + " and flags " + flags);
+            }
+
+            if ((flags & FLAG_UPDATE_UI_HIDE) != 0) {
+                // TODO(b/33197203): handle it?
+                if (DEBUG) Slog.d(TAG, "ignoring FLAG_UPDATE_UI_HIDE request for " + autoFillId);
+
+                return;
+            }
+
+            session.mCurrentAutoFillId = autoFillId;
+            session.mCurrentBounds = bounds;
+            mUi.showResponse(mUserId, session.mId, autoFillId, bounds, session.mCurrentResponse);
             return;
         }
 
         final int sessionId = ++sSessionIdCounter;
-        final Session session = new Session(sessionId, extras);
-        mSessions.put(sessionId, session);
+        if (DEBUG) {
+            Slog.d(TAG, "requestAutoFillLocked(): new session (id=" + sessionId + " for token "
+                    + activityToken + " and autoFillId " + autoFillId);
+        }
+
+        final Session newSession = new Session(sessionId, activityToken, autoFillId, bounds);
+        mSessions.put(sessionId, newSession);
+
+        final String historyItem = "s=" + mComponentName + " u=" + mUserId + " f=" + flags
+                + " a=" + activityToken + " i=" + autoFillId + " b=" + bounds;
+        mRequestsHistory.log(historyItem);
 
         /*
          * TODO(b/33197203): apply security checks below:
@@ -324,10 +359,79 @@
                 Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
             }
         } catch (RemoteException e) {
-            // Should happen, it's a local call.
+            // Should not happen, it's a local call.
         }
     }
 
+    /**
+     * Called by UI to trigger a save request to the service.
+     */
+    void requestSaveLocked(int sessionId) {
+        // TODO(b/33197203): add MetricsLogger call
+        // TODO(b/33197203): use handler?
+        // TODO(b/33197203): show error on UI on Slog.w situations below???
+
+        if (mService == null) {
+            Slog.w(TAG, "requestSave(): service is null");
+            return;
+        }
+        final Session session = mSessions.get(sessionId);
+        if (session == null) {
+            Slog.w(TAG, "requestSave(): no session with id " + sessionId);
+            return;
+        }
+        final IBinder activityToken = session.mActivityToken.get();
+        if (activityToken == null) {
+            Slog.w(TAG, "activity token for session " + sessionId + " already GCed");
+            return;
+        }
+
+        /*
+         * TODO(b/33197203): apply security checks below:
+         * - checks if disabled by secure settings / device policy
+         * - log operation using noteOp()
+         * - check flags
+         * - display disclosure if needed
+         */
+        try {
+            /* TODO(b/33197203): refactor save logic so it uses a cached AssistStructure, and get
+               the extras to be sent to the service based on the response / dataset in the session.
+               Something like:
+           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 notification: " +
+                        bundleToString(datasetExtras));
+                extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
+            }
+
+             */
+
+            if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken,
+                    AUTO_FILL_FLAG_TYPE_SAVE)) {
+                Slog.w(TAG, "failed to save for " + activityToken);
+            }
+        } catch (RemoteException e) {
+            // Should not happen, it's a local call.
+        }
+    }
+
+    private Session getSessionByTokenLocked(IBinder activityToken) {
+        final int size = mSessions.size();
+        for (int i = 0; i < size; i++) {
+            final Session session = mSessions.valueAt(i);
+            if (activityToken.equals(session.mActivityToken.get())) {
+                return session;
+            }
+        }
+        return null;
+    }
+
     void stopLocked() {
         if (DEBUG) Slog.d(TAG, "stopLocked()");
 
@@ -443,12 +547,13 @@
 
         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("mComponent="); pw.println(mComponentName);
         pw.print(prefix); pw.print("mService: "); pw.println(mService);
         pw.print(prefix); pw.print("mBound="); pw.println(mBound);
         pw.print(prefix); pw.print("mEstimateTimeOfDeath=");
             TimeUtils.formatDuration(mEstimateTimeOfDeath, SystemClock.uptimeMillis(), pw);
-        pw.println();
+            pw.println();
+        pw.print(prefix); pw.print("mAuthToken: "); pw.println(mAuthToken);
 
         if (DEBUG) {
             // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
@@ -456,14 +561,6 @@
             mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
         }
 
-        if (mRequestHistory.isEmpty()) {
-            pw.print(prefix); pw.println("No history");
-        } else {
-            pw.print(prefix); pw.println("History:");
-            for (int i = 0; i < mRequestHistory.size(); i++) {
-                pw.print(prefix2); pw.print(i); pw.print(": "); pw.println(mRequestHistory.get(i));
-            }
-        }
         if (mQueuedRequests.isEmpty()) {
             pw.print(prefix); pw.println("No queued requests");
         } else {
@@ -480,38 +577,37 @@
         } else {
             pw.print(prefix); pw.print(size); pw.println(" sessions:");
             for (int i = 0; i < size; i++) {
-                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(session.mAppCallback) ; pw.println(")");
-                }
+                pw.print(prefix); pw.print("#"); pw.println(i + 1);
+                mSessions.valueAt(i).dumpLocked(prefix2, pw);
             }
-            pw.println();
         }
     }
 
     @Override
     public String toString() {
         return "AutoFillManagerServiceImpl: [userId=" + mUserId + ", uid=" + mUid
-                + ", component=" + mComponent.flattenToShortString() + "]";
+                + ", component=" + mComponentName + "]";
     }
 
     private static final class QueuedRequest {
         final IBinder activityToken;
-        final Bundle extras;
+        final AutoFillId autoFillId;
+        final Rect bounds;
         final int flags;
 
-        QueuedRequest(IBinder activityToken, Bundle extras, int flags) {
+        QueuedRequest(IBinder activityToken, AutoFillId autoFillId, Rect bounds, int flags) {
             this.activityToken = activityToken;
-            this.extras = extras;
+            this.autoFillId = autoFillId;
+            this.bounds = bounds;
             this.flags = flags;
         }
 
         @Override
         public String toString() {
-            return "flags: " + flags + " token: " + activityToken;
+            if (!DEBUG) return super.toString();
+
+            return "QueuedRequest: [flags=" + flags + ", token=" + activityToken
+                    + ", id=" + autoFillId + ", bounds=" + bounds;
         }
     }
 
@@ -532,11 +628,17 @@
     private final class Session {
 
         private final int mId;
-        private final Bundle mExtras;
+        private final WeakReference<IBinder> mActivityToken;
+
         private IAutoFillAppCallback mAppCallback;
 
-        // Token used on fingerprint authentication
-        private final IBinder mToken = new Binder();
+        // Current view where the auto-fill bar is displayed
+        @GuardedBy("mLock")
+        private AutoFillId mCurrentAutoFillId;
+        @GuardedBy("mLock")
+        private Rect mCurrentBounds;
+        @GuardedBy("mLock")
+        private FillResponse mCurrentResponse;
 
         private final IFingerprintService mFingerprintService;
 
@@ -710,13 +812,28 @@
             }
         };
 
-        private Session(int id, Bundle extras) {
+        private Session(int id, IBinder activityToken, AutoFillId autoFillId, Rect bounds) {
             this.mId = id;
-            this.mExtras = extras;
+            this.mActivityToken = new WeakReference<>(activityToken);
+            this.mCurrentAutoFillId = autoFillId;
+            this.mCurrentBounds = bounds;
             this.mFingerprintService = IFingerprintService.Stub
                     .asInterface(ServiceManager.getService("fingerprint"));
         }
 
+        void setAppCallback(IBinder appBinder) {
+            try {
+                appBinder.linkToDeath(() -> {
+                    if (DEBUG) Slog.d(TAG, "app callback died");
+                    // TODO(b/33197203): more cleanup here?
+                    mAppCallback = null;
+                }, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "linkToDeath() failed: " + e);
+            }
+            mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+        }
+
         private void showResponseLocked(FillResponse response, boolean authRequired) {
             if (DEBUG) Slog.d(TAG, "showResponse(directly=" + mAutoFillDirectly
                     + ", authRequired=" + authRequired +"):" + response);
@@ -735,7 +852,8 @@
 
             if (!authRequired) {
                 // TODO(b/33197203): add MetricsLogger call
-                mUi.showOptions(mUserId, mId, response);
+                mCurrentResponse = response;
+                mUi.showResponse(mUserId, mId, mCurrentAutoFillId, mCurrentBounds, mCurrentResponse);
                 return;
             }
 
@@ -768,8 +886,8 @@
                 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.
+                    // 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.
@@ -785,14 +903,27 @@
             }
         }
 
+        void dumpLocked(String prefix, PrintWriter pw) {
+            pw.print(prefix); pw.print("mId: "); pw.println(mId);
+            pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken.get());
+            pw.print(prefix); pw.print("mCurrentAutoFillId: "); pw.println(mCurrentAutoFillId);
+            pw.print(prefix); pw.print("mCurrentBounds: "); pw.println(mCurrentBounds);
+            pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
+            pw.print(prefix);
+                pw.print("mResponseRequiringAuth: "); pw.println(mResponseRequiringAuth);
+            pw.print(prefix);
+                pw.print("mDatasetRequiringAuth: "); pw.println(mDatasetRequiringAuth);
+            pw.print(prefix); pw.print("mAutoFillDirectly: "); pw.println(mAutoFillDirectly);
+        }
+
         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);
+                // TODO(b/33197203): temporarily hack: show the save notification after autofilled,
+                // since save is not automatically detected yet.
+                mUi.showSaveNotification(mUserId, mId); removeSelf = false;
 
             } catch (RemoteException e) {
                 Slog.w(TAG, "Error auto-filling activity: " + e);
@@ -812,7 +943,8 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 // TODO(b/33197203): set a timeout?
-                mFingerprintService.authenticate(mToken, opId, mUserId, mServiceReceiver, 0, null);
+                mFingerprintService.authenticate(mAuthToken, opId, mUserId, mServiceReceiver, 0,
+                        null);
             } catch (RemoteException e) {
                 // Local call, shouldn't happen.
             } finally {
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
index 26f2451..4998e3f 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -74,7 +74,7 @@
 
     private int requestAutoFill(int flags) throws RemoteException {
         final int userId = getUserIdFromArgs();
-        mService.requestAutoFill(null, userId, null, flags);
+        mService.requestAutoFillForUser(userId, flags);
         return 0;
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index ad525d4..511d3d9 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -15,11 +15,8 @@
  */
 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.Helper.DEBUG;
-import static com.android.server.autofill.Helper.bundleToString;
 
 import android.app.Activity;
 import android.app.Notification;
@@ -31,18 +28,23 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Rect;
+import android.graphics.PixelFormat;
 import android.os.Binder;
 import android.os.Bundle;
-import android.service.autofill.AutoFillService;
 import android.util.Slog;
 import android.view.autofill.AutoFillId;
 import android.view.autofill.Dataset;
 import android.view.autofill.FillResponse;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
 import android.widget.Toast;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.UiThread;
 
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.List;
 
@@ -56,8 +58,16 @@
 
     private final Context mContext;
 
+    private final WindowManager mWm;
+
+    /**
+     * Custom snackbar UI used for saving autofill or other informational messages.
+     */
+    private View mSnackbar;
+
     AutoFillUI(Context context, AutoFillManagerService service, Object lock) {
         mContext = context;
+        mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         mService = service;
         mLock = lock;
 
@@ -87,12 +97,15 @@
 
     /**
      * Shows the options from a {@link FillResponse} so the user can pick up the proper
-     * {@link Dataset} (when the response has one).
+     * {@link Dataset} (when the response has one) for a given view (identified by
+     * {@code autoFillId}).
      */
-    void showOptions(int userId, int sessionId, FillResponse response) {
+    void showResponse(int userId, int sessionId, AutoFillId autoFillId, Rect bounds,
+            FillResponse response) {
+        if (DEBUG) Slog.d(TAG, "showResponse: id=" + autoFillId +  ", bounds=" + bounds);
         // TODO(b/33197203): proper implementation
-        // TODO(b/33197203): make sure if removes the callback from cache
-        showOptionsNotification(userId, sessionId, response);
+        // TODO(b/33197203): make sure if removes the session from cache
+        showOptionsNotification(userId, sessionId, autoFillId, response);
     }
 
     /**
@@ -127,6 +140,26 @@
     }
 
     /**
+     * Shows the UI asking the user to save for auto-fill.
+     */
+    void showSaveUI(int userId, int sessionId) {
+        showSnackbar(new SavePrompt(mContext, new SavePrompt.OnSaveListener() {
+            @Override
+            public void onSaveClick() {
+                hideSnackbar();
+                synchronized (mLock) {
+                    final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+                    service.requestSaveLocked(sessionId);
+                }
+            }
+            @Override
+            public void onCancelClick() {
+                hideSnackbar();
+            }
+        }));
+    }
+
+    /**
      * Called by service after the user user the fingerprint sensors to authenticate.
      */
     void dismissFingerprintRequest(int userId, boolean success) {
@@ -142,6 +175,13 @@
         }
     }
 
+    void dump(PrintWriter pw) {
+        pw.println("AufoFill UI");
+        final String prefix = "  ";
+        pw.print(prefix); pw.print("sResultCode: "); pw.println(sResultCode);
+        pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar);
+    }
+
     private AutoFillManagerServiceImpl getServiceLocked(int userId) {
         final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
         if (service == null) {
@@ -150,27 +190,9 @@
         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 onSaveRequested(int userId, int sessionId) {
+        // TODO(b/33197203): displays the snack bar, until save notification is refactored
+        showSaveUI(userId, sessionId);
     }
 
     private void onDatasetPicked(int userId, Dataset dataset, int sessionId) {
@@ -200,6 +222,34 @@
         }
     }
 
+    //similar to a snackbar, but can be a bit custom since it is more than just text. This will
+    //allow two buttons for saving or not saving the autofill for instance as well.
+    private void showSnackbar(View snackBar) {
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+            WindowManager.LayoutParams.FILL_PARENT,
+            WindowManager.LayoutParams.WRAP_CONTENT,
+            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, // TODO(b/33197203) use TYPE_AUTO_FILL
+            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN,
+            PixelFormat.TRANSLUCENT);
+
+        params.gravity = Gravity.BOTTOM | Gravity.LEFT;
+
+        UiThread.getHandler().runWithScissors(() -> {
+            mSnackbar = snackBar;
+            mWm.addView(mSnackbar, params);
+        }, 0);
+    }
+
+    private void hideSnackbar() {
+        UiThread.getHandler().runWithScissors(() -> {
+            if (mSnackbar != null) {
+                mWm.removeView(mSnackbar);
+                mSnackbar = null;
+            }
+        }, 0);
+    }
+
     /////////////////////////////////////////////////////////////////////////////////
     // TODO(b/33197203): temporary code using a notification to request auto-fill. //
     // Will be removed once UX decide the right way to present it to the user.     //
@@ -224,9 +274,9 @@
     private static final String TYPE_SAVE = "save";
     private static final String TYPE_AUTH_RESPONSE = "auth_response";
 
-    @GuardedBy("mLock")
+    @GuardedBy("mServiceLock")
     private BroadcastReceiver mNotificationReceiver;
-    @GuardedBy("mLock")
+    @GuardedBy("mServiceLock")
     private final AutoFillManagerService mService;
     private final Object mLock;
 
@@ -253,10 +303,7 @@
                 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
@@ -264,7 +311,7 @@
             synchronized (mLock) {
                 switch (type) {
                     case TYPE_SAVE:
-                        onSaveRequested(userId, responseExtras, datasetExtras);
+                        onSaveRequested(userId, sessionId);
                         break;
                     case TYPE_FINISH_SESSION:
                         onSessionDone(userId, sessionId);
@@ -315,17 +362,18 @@
      * 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 sessionId, FillResponse response) {
+    private void showOptionsNotification(int userId, int callbackId, AutoFillId autoFillId,
+            FillResponse response) {
         final long token = Binder.clearCallingIdentity();
         try {
-            showOptionsNotificationAsSystem(userId, sessionId, response);
+            showOptionsNotificationAsSystem(userId, callbackId, autoFillId, response);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
     private void showOptionsNotificationAsSystem(int userId, int sessionId,
-            FillResponse response) {
+            AutoFillId autoFillId, FillResponse response) {
         // Make sure server callback is removed from cache if user cancels the notification.
         final Intent deleteIntent = newNotificationIntent(userId, TYPE_FINISH_SESSION)
                 .putExtra(EXTRA_SESSION_ID, sessionId);
@@ -352,13 +400,13 @@
         }
         boolean showSave = false;
         if (datasets == null ) {
-            subTitle = "No options to auto-fill this activity.";
+            subTitle = "No options to auto-fill " + autoFillId;
         } else if (datasets.isEmpty()) {
             if (savableIds.length == 0) {
-                subTitle = "No options to auto-fill this activity.";
+                subTitle = "No options to auto-fill " + autoFillId;
             } else {
-                subTitle = "No options to auto-fill this activity, but provider can save ids:\n"
-                        + Arrays.toString(savableIds);
+                subTitle = "No options to auto-fill " + autoFillId
+                        + ", but provider can save ids:\n" + Arrays.toString(savableIds);
                 showSave = true;
             }
         } else {
@@ -369,7 +417,7 @@
             } else {
                 autoCancel = false;
                 final int size = datasets.size();
-                subTitle = "There are " + size + " option(s).\n"
+                subTitle = "There are " + size + " option(s) to fill " + autoFillId + ".\n"
                         + "Use the notification action(s) to select the proper one."
                         + "Actions with (F) require fingerprint unlock, and with (P) require"
                         + "provider authentication to unlock";
@@ -394,36 +442,28 @@
         NotificationManager.from(mContext).notify(TYPE_OPTIONS, userId, notification.build());
 
         if (showSave) {
-            showSaveNotification(userId, response, null);
+            showSaveNotification(userId, sessionId);
         }
     }
 
-    void showSaveNotification(int userId, FillResponse response, Dataset dataset) {
+    void showSaveNotification(int userId, int sessionId) {
         final long token = Binder.clearCallingIdentity();
         try {
-            showSaveNotificationAsSystem(userId, response, dataset);
+            showSaveNotificationAsSystem(userId, sessionId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    private void showSaveNotificationAsSystem(int userId, FillResponse response, Dataset dataset) {
-        final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE);
-        if (response != null) {
-            saveIntent.putExtra(EXTRA_FILL_RESPONSE, response);
-        }
-        if (dataset != null) {
-            saveIntent.putExtra(EXTRA_DATASET, dataset);
-        }
+    private void showSaveNotificationAsSystem(int userId, int sessionId) {
+        final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE)
+                .putExtra(EXTRA_SESSION_ID, sessionId);
+
         final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext,
                 ++sResultCode, saveIntent, PendingIntent.FLAG_ONE_SHOT);
 
-        final String title = "AutoFill Save";
-        // 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 String title = "AutoFill Save Emulation";
+        final String subTitle = "Tap notification to launch the save snackbar.";
 
         final Notification notification = newNotificationBuilder()
                 .setAutoCancel(true)
diff --git a/services/autofill/java/com/android/server/autofill/DatasetPicker.java b/services/autofill/java/com/android/server/autofill/DatasetPicker.java
new file mode 100644
index 0000000..bb64178
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/DatasetPicker.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import static com.android.server.autofill.Helper.DEBUG;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Slog;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.autofill.Dataset;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * View responsible for drawing the {@link Dataset} options that can be used to auto-fill an
+ * {@link Activity}.
+ */
+final class DatasetPicker extends LinearLayout {
+
+    private static final String TAG = "DatasetPicker";
+
+    // TODO(b/33197203): use / calculate proper values instead of hardcoding them
+    private static final LayoutParams NAME_PARAMS = new LayoutParams(400,
+            WindowManager.LayoutParams.WRAP_CONTENT);
+    private static final LayoutParams DROP_DOWN_PARAMS = new LayoutParams(100,
+            WindowManager.LayoutParams.WRAP_CONTENT);
+
+    private final Line[] mLines;
+
+    private boolean mExpanded;
+    private final Listener mListener;
+
+    public DatasetPicker(Context context, Listener listener, List<Dataset> datasets) {
+        super(context);
+
+        mListener = listener;
+
+        // TODO(b/33197203): use XML layout
+        setOrientation(LinearLayout.VERTICAL);
+
+        final int size = datasets.size();
+        mLines = new Line[size];
+
+        for (int i = 0; i < size; i++) {
+            final boolean first = i == 0;
+            final Line line = new Line(context, datasets.get(i), first);
+            mLines[i] = line;
+            if (first) {
+                addView(line);
+            }
+        }
+        mExpanded = false;
+    }
+
+    private void togleDropDown() {
+        if (mExpanded) {
+            hideDropDown();
+            return;
+        }
+        for (int i = 1; i < mLines.length; i++) {
+            addView(mLines[i]);
+        }
+        mExpanded = true;
+    }
+
+    private void hideDropDown() {
+        if (!mExpanded) return;
+        // TODO(b/33197203): invert order to be less janky?
+        for (int i = 1; i < mLines.length; i++) {
+            removeView(mLines[i]);
+        }
+        mExpanded = false;
+    }
+
+    private class Line extends LinearLayout {
+        final TextView name;
+        final ImageView dropDown;
+
+        private Line(Context context, Dataset dataset, boolean first) {
+            super(context);
+
+            final View.OnClickListener l = new View.OnClickListener() {
+
+                @Override
+                public void onClick(View v) {
+                    if (DEBUG) Slog.d(TAG, "dataset picked: " + dataset.getName());
+                    mListener.onDatasetPicked(dataset);
+
+                }
+            };
+
+            // TODO(b/33197203): use XML layout
+            setOrientation(LinearLayout.HORIZONTAL);
+
+            name = new TextView(context);
+            name.setLayoutParams(NAME_PARAMS);
+            name.setText(dataset.getName());
+            name.setOnClickListener(l);
+
+            dropDown = new ImageView(context);
+            dropDown.setLayoutParams(DROP_DOWN_PARAMS);
+            // TODO(b/33197203): use proper icon
+            dropDown.setImageResource(com.android.internal.R.drawable.arrow_down_float);
+            dropDown.setOnClickListener((v) -> {
+                togleDropDown();
+            });
+
+            if (!first) {
+                dropDown.setVisibility(View.INVISIBLE);
+            }
+
+            addView(name);
+            addView(dropDown);
+        }
+    }
+
+    static interface Listener {
+        void onDatasetPicked(Dataset dataset);
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/SavePrompt.java b/services/autofill/java/com/android/server/autofill/SavePrompt.java
new file mode 100644
index 0000000..f0b51e2
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/SavePrompt.java
@@ -0,0 +1,60 @@
+/*
+ * 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.content.Context;
+import android.graphics.Color;
+import android.widget.RelativeLayout;
+import android.widget.RelativeLayout.LayoutParams;
+import android.widget.TextView;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.R;
+
+/**
+ * Autofill Save Prompt
+ */
+final class SavePrompt extends RelativeLayout {
+    public interface OnSaveListener {
+        void onSaveClick();
+        void onCancelClick();
+    }
+
+    private final TextView mNoButton;
+    private final TextView mYesButton;
+    private final OnSaveListener mListener;
+
+    SavePrompt(Context context, OnSaveListener listener) {
+        super(context);
+        mListener = listener;
+        LayoutInflater inflater = LayoutInflater.from(context);
+        View view = inflater.inflate(R.layout.autofill_save, this);
+
+        mNoButton = (TextView) view.findViewById(R.id.autofill_save_no);
+        mNoButton.setOnClickListener((v) -> {
+            mListener.onCancelClick();
+        });
+
+        mYesButton = (TextView) view.findViewById(R.id.autofill_save_yes);
+        mYesButton.setOnClickListener((v) -> {
+            mListener.onSaveClick();
+        });
+
+        //addView(view);
+    }
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index f3f8da8..1f8702a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -690,11 +690,6 @@
     }
     private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
 
-    @VisibleForTesting
-    protected HandlerThread createHandlerThread() {
-        return new HandlerThread("ConnectivityServiceThread");
-    }
-
     public ConnectivityService(Context context, INetworkManagementService netManager,
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
         this(context, netManager, statsService, policyManager, new IpConnectivityLog());
@@ -715,7 +710,7 @@
         mDefaultMobileDataRequest = createInternetRequestForTransport(
                 NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
 
-        mHandlerThread = createHandlerThread();
+        mHandlerThread = new HandlerThread("ConnectivityServiceThread");
         mHandlerThread.start();
         mHandler = new InternalHandler(mHandlerThread.getLooper());
         mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index bee1f97..c4666dc 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -926,7 +926,7 @@
             return;
         }
         final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
-                context, mSystemReady, mSettings.getEnabledInputMethodListLocked());
+                context, mSettings.getEnabledInputMethodListLocked());
         if (suitableImes.isEmpty()) {
             Slog.i(TAG, "No default found");
             return;
@@ -3089,7 +3089,7 @@
 
         if (resetDefaultEnabledIme) {
             final ArrayList<InputMethodInfo> defaultEnabledIme =
-                    InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, mMethodList);
+                    InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList);
             final int N = defaultEnabledIme.size();
             for (int i = 0; i < N; ++i) {
                 final InputMethodInfo imi =  defaultEnabledIme.get(i);
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 6cc72de..4ab894f 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -132,6 +132,8 @@
     private static final String FUSED_LOCATION_SERVICE_ACTION =
             "com.android.location.service.FusedLocationProvider";
 
+    private static final String GMSCORE_PACKAGE = "com.android.google.gms";
+
     private static final int MSG_LOCATION_CHANGED = 1;
 
     private static final long NANOS_PER_MILLI = 1000000L;
@@ -140,7 +142,7 @@
     private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
 
     // default background throttling interval if not overriden in settings
-    private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 1000;
+    private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 10 * 60 * 1000;
 
     // Location Providers may sometimes deliver location updates
     // slightly faster that requested - provide grace period so
@@ -214,10 +216,13 @@
     private final HashMap<String, Location> mLastLocationCoarseInterval =
             new HashMap<>();
 
-    // all providers that operate over proxy, for authorizing incoming location
+    // all providers that operate over proxy, for authorizing incoming location and whitelisting
+    // throttling
     private final ArrayList<LocationProviderProxy> mProxyProviders =
             new ArrayList<>();
 
+    private String[] mBackgroundThrottlePackageWhitelist = new String[]{};
+
     // current active user on the device - other users are denied location data
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
     private int[] mCurrentUserProfiles = new int[] { UserHandle.USER_SYSTEM };
@@ -359,6 +364,26 @@
                         }
                     }
                 }, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+            Settings.Global.getUriFor(
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
+            true,
+            new ContentObserver(mLocationHandler) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    synchronized (mLock) {
+                        String setting = Settings.Global.getString(
+                            mContext.getContentResolver(),
+                            Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+                        if (setting == null) {
+                            setting = "";
+                        }
+
+                        mBackgroundThrottlePackageWhitelist = setting.split(",");
+                        updateProvidersLocked();
+                    }
+                }
+            }, UserHandle.USER_ALL);
         mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
 
         // listen for user change
@@ -1066,19 +1091,6 @@
         mProvidersByName.remove(provider.getName());
     }
 
-    private boolean isOverlayProviderPackageLocked(String packageName) {
-        for (LocationProviderInterface provider : mProviders) {
-            if (provider instanceof LocationProviderProxy) {
-                if (packageName.equals(
-                        ((LocationProviderProxy) provider).getConnectedPackageName())) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
     /**
      * Returns "true" if access to the specified location provider is allowed by the current
      * user's settings. Access to all location providers is forbidden to non-location-provider
@@ -1542,8 +1554,28 @@
         p.setRequest(providerRequest, worksource);
     }
 
-    private boolean isThrottlingExemptLocked(Receiver recevier) {
-        return isOverlayProviderPackageLocked(recevier.mPackageName);
+    private boolean isThrottlingExemptLocked(Receiver receiver) {
+        if (receiver.mUid == Process.SYSTEM_UID) {
+            return true;
+        }
+
+        if (receiver.mPackageName.equals(GMSCORE_PACKAGE)) {
+            return true;
+        }
+
+        for (LocationProviderProxy provider : mProxyProviders) {
+            if (receiver.mPackageName.equals(provider.getConnectedPackageName())) {
+                return true;
+            }
+        }
+
+        for (String whitelistedPackage : mBackgroundThrottlePackageWhitelist) {
+            if (receiver.mPackageName.equals(whitelistedPackage)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     private class UpdateRecord {
@@ -1766,7 +1798,7 @@
         if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
                 + " " + name + " " + request + " from " + packageName + "(" + uid + " "
                 + (record.mIsForegroundUid ? "foreground" : "background")
-                + (isOverlayProviderPackageLocked(receiver.mPackageName) ? " [whitelisted]" : "") + ")");
+                + (isThrottlingExemptLocked(receiver) ? " [whitelisted]" : "") + ")");
 
         UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
         if (oldRecord != null) {
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index d51e96a..c77a407 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -19,6 +19,8 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
 import android.os.Build;
 import android.os.RecoverySystem;
 import android.os.SystemClock;
@@ -57,15 +59,31 @@
     private static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
     private static final int LEVEL_FACTORY_RESET = 4;
 
-    private static final boolean DISABLE_RESET_SETTINGS = true;
-
     /** Threshold for boot loops */
     private static final Threshold sBoot = new BootThreshold();
     /** Threshold for app crash loops */
     private static SparseArray<Threshold> sApps = new SparseArray<>();
 
     private static boolean isDisabled() {
-        return Build.IS_ENG || SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false);
+        // We're disabled on all engineering devices
+        if (Build.IS_ENG) return true;
+
+        // We're disabled on userdebug devices connected over USB, since that's
+        // a decent signal that someone is actively trying to debug the device,
+        // or that it's in a lab environment.
+        if (Build.IS_USERDEBUG) {
+            try {
+                if (LocalServices.getService(BatteryManagerInternal.class)
+                        .getPlugType() == BatteryManager.BATTERY_PLUGGED_USB) {
+                    return true;
+                } else {
+                }
+            } catch (Throwable ignored) {
+            }
+        }
+
+        // One last-ditch check
+        return SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false);
     }
 
     /**
@@ -161,8 +179,6 @@
     }
 
     private static void resetAllSettings(Context context, int mode) throws Exception {
-        if (DISABLE_RESET_SETTINGS) return;
-
         // Try our best to reset all settings possible, and once finished
         // rethrow any exception that we encountered
         Exception res = null;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c07add0..629da86 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -108,6 +108,8 @@
 import com.android.server.NativeDaemonConnector.SensitiveArg;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.storage.AppFuseBridge;
+import com.android.server.storage.FileCollector;
+
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
 
@@ -817,6 +819,9 @@
     }
 
     private void handleSystemReady() {
+        // Register kernel mapping from extensions to statistics GIDs
+        FileCollector.updateKernelExtensions();
+
         initIfReadyAndConnected();
         resetIfReadyAndConnected();
 
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index cbd7be7..2b5166e 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -198,7 +198,7 @@
                 mUserManager.getProfileIdsWithDisabled(mSettings.getCurrentUserId()));
     }
 
-    private class TextServicesMonitor extends PackageMonitor {
+    private final class TextServicesMonitor extends PackageMonitor {
         private boolean isChangingPackagesOfCurrentUser() {
             final int userId = getChangingUserId();
             final boolean retval = userId == mSettings.getCurrentUserId();
@@ -236,7 +236,7 @@
         }
     }
 
-    class TextServicesBroadcastReceiver extends BroadcastReceiver {
+    private final class TextServicesBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
@@ -618,8 +618,7 @@
             Slog.w(TAG, "Start spell checker session inner locked.");
         }
         final String sciId = info.getId();
-        final InternalServiceConnection connection = new InternalServiceConnection(
-                sciId, locale, bundle);
+        final InternalServiceConnection connection = new InternalServiceConnection(sciId);
         final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
         serviceIntent.setComponent(info.getComponent());
         if (DBG) {
@@ -836,7 +835,7 @@
     // SpellCheckerBindGroup contains active text service session listeners.
     // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
     // mSpellCheckerBindGroups
-    private class SpellCheckerBindGroup {
+    private final class SpellCheckerBindGroup {
         private final String TAG = SpellCheckerBindGroup.class.getSimpleName();
         private final InternalServiceConnection mInternalConnection;
         private final CopyOnWriteArrayList<InternalDeathRecipient> mListeners =
@@ -972,15 +971,10 @@
         }
     }
 
-    private class InternalServiceConnection implements ServiceConnection {
+    private final class InternalServiceConnection implements ServiceConnection {
         private final String mSciId;
-        private final String mLocale;
-        private final Bundle mBundle;
-        public InternalServiceConnection(
-                String id, String locale, Bundle bundle) {
+        public InternalServiceConnection(String id) {
             mSciId = id;
-            mLocale = locale;
-            mBundle = bundle;
         }
 
         @Override
@@ -1013,7 +1007,7 @@
         }
     }
 
-    private class InternalDeathRecipient implements IBinder.DeathRecipient {
+    private static final class InternalDeathRecipient implements IBinder.DeathRecipient {
         public final ITextServicesSessionListener mTsListener;
         public final ISpellCheckerSessionListener mScListener;
         public final String mScLocale;
@@ -1041,7 +1035,7 @@
         }
     }
 
-    private static class TextServicesSettings {
+    private static final class TextServicesSettings {
         private final ContentResolver mResolver;
         @UserIdInt
         private int mCurrentUserId;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 42d036c..140ae9b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19603,10 +19603,15 @@
 
     /** Helper method that requests bounds from WM and applies them to stack. */
     private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) {
-        final Rect newBounds = mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration();
+        final Rect newStackBounds = new Rect();
+        final Rect newTempTaskBounds = new Rect();
+        mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration(newStackBounds,
+                newTempTaskBounds);
         mStackSupervisor.resizeStackLocked(
-                stackId, newBounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
-                false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);
+                stackId, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */,
+                !newTempTaskBounds.isEmpty() ? newTempTaskBounds : null /* tempTaskBounds */,
+                null /* tempTaskInsetBounds */, false /* preserveWindows */,
+                false /* allowResizeInDockedMode */, deferResume);
     }
 
     /**
@@ -22598,6 +22603,13 @@
         }
 
         @Override
+        public IBinder getTopVisibleActivity(int uid) {
+            synchronized (ActivityManagerService.this) {
+                return mStackSupervisor.getTopVisibleActivity(uid);
+            }
+        }
+
+        @Override
         public void notifyDockedStackMinimizedChanged(boolean minimized) {
             synchronized (ActivityManagerService.this) {
                 mStackSupervisor.setDockedStackMinimized(minimized);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 8ca77c5..baf7772 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -729,7 +729,6 @@
         if (mWindowContainerController != null) {
             throw new IllegalArgumentException("Window container=" + mWindowContainerController
                     + " already created for r=" + this);
-
         }
 
         inHistory = true;
@@ -754,6 +753,26 @@
         mWindowContainerController = null;
     }
 
+    /**
+     * Reparents this activity into {@param newTask} at the provided {@param position}.  The caller
+     * should ensure that the {@param newTask} is not already the parent of this activity.
+     */
+    void reparent(TaskRecord newTask, int position, String reason) {
+        final TaskRecord prevTask = task;
+        if (prevTask == newTask) {
+            throw new IllegalArgumentException(reason + ": task=" + newTask
+                    + " is already the parent of r=" + this);
+        }
+
+        // Must reparent first in window manager
+        mWindowContainerController.reparent(newTask.getWindowContainerController(), position);
+
+        // Remove the activity from the old task and add it to the new task
+        prevTask.removeActivity(this);
+        setTask(newTask, null);
+        newTask.addActivityAtIndex(position, this);
+    }
+
     private boolean isHomeIntent(Intent intent) {
         return Intent.ACTION_MAIN.equals(intent.getAction())
                 && intent.hasCategory(Intent.CATEGORY_HOME)
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 98b5835c..9ce7ae30 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -26,6 +26,8 @@
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
+
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_APP;
@@ -542,8 +544,10 @@
         mWindowContainerController.setPictureInPictureAspectRatio(aspectRatio);
     }
 
-    void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) {
-        mWindowContainerController.getStackDockedModeBounds(outBounds, ignoreVisibility);
+    void getStackDockedModeBounds(Rect outBounds, Rect outTempBounds, Rect outTempInsetBounds,
+            boolean ignoreVisibility) {
+        mWindowContainerController.getStackDockedModeBounds(outBounds, outTempBounds,
+                outTempInsetBounds, ignoreVisibility);
     }
 
     void prepareFreezingTaskBounds() {
@@ -562,8 +566,8 @@
         outBounds.setEmpty();
     }
 
-    Rect getBoundsForNewConfiguration() {
-        return mWindowContainerController.getBoundsForNewConfiguration();
+    void getBoundsForNewConfiguration(Rect outBounds, Rect outTempBounds) {
+        mWindowContainerController.getBoundsForNewConfiguration(outBounds, outTempBounds);
     }
 
     void positionChildWindowContainerAtTop(TaskRecord child) {
@@ -1882,6 +1886,12 @@
             if (isTop) {
                 mTopActivityOccludesKeyguard |= showWhenLocked;
             }
+
+            final boolean canShowWithKeyguard = canShowWithInsecureKeyguard()
+                    && mStackSupervisor.mKeyguardController.canDismissKeyguard();
+            if (canShowWithKeyguard) {
+                return true;
+            }
         }
         if (keyguardShowing) {
 
@@ -1897,6 +1907,22 @@
         }
     }
 
+    /**
+     * Check if the display to which this stack is attached has
+     * {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
+     */
+    private boolean canShowWithInsecureKeyguard() {
+        final ActivityStackSupervisor.ActivityDisplay activityDisplay
+                = mActivityContainer.mActivityDisplay;
+        if (activityDisplay == null) {
+            throw new IllegalStateException("Stack is not attached to any display, stackId="
+                    + mStackId);
+        }
+
+        final int flags = activityDisplay.mDisplay.getFlags();
+        return (flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0;
+    }
+
     private void checkTranslucentActivityWaiting(ActivityRecord top) {
         if (mTranslucentActivityWaiting != top) {
             mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -3043,8 +3069,7 @@
                             + " to task=" + task + ":" + taskInsertionPoint);
                     for (int srcPos = start; srcPos >= i; --srcPos) {
                         final ActivityRecord p = activities.get(srcPos);
-                        p.setTask(task, null);
-                        task.addActivityAtIndex(taskInsertionPoint, p);
+                        p.reparent(task, taskInsertionPoint, "resetAffinityTaskIfNeededLocked");
 
                         if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE,
                                 "Removing and adding activity " + p + " to stack at " + task
@@ -5071,12 +5096,17 @@
         final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
         final boolean wasPaused = prevStack.mPausingActivity == r;
 
+        // Create a new task for the activity to be parented in
         final TaskRecord task = createTaskRecord(
                 mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
                 r.info, r.intent, null, null, true, r.mActivityType);
-        r.setTask(task, null);
-        task.addActivityToTop(r);
+        // This is a new task, so reparenting it to position 0 will move it to the top
+        r.reparent(task, 0 /* position */, "moveActivityToStack");
+
+        // Notify the task actiivties if it was moved to/from a pinned stack
         mStackSupervisor.scheduleReportPictureInPictureModeChangedIfNeeded(task, prevStack);
+
+        // Resume the activity if necessary after it has moved
         moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, wasPaused,
                 "moveActivityToStack");
         if (wasResumed) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 389fb8b..b2b3e61 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -418,8 +418,6 @@
 
     final ActivityMetricsLogger mActivityMetricsLogger;
 
-    private final ResizeDockedStackTimeout mResizeDockedStackTimeout;
-
     @Override
     protected int getChildCount() {
         return mActivityDisplays.size();
@@ -529,7 +527,6 @@
         mService = service;
         mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper());
         mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
-        mResizeDockedStackTimeout = new ResizeDockedStackTimeout(service, this, mHandler);
         mKeyguardController = new KeyguardController(service, this);
     }
 
@@ -2347,13 +2344,19 @@
                 // static stacks need to be adjusted so they don't overlap with the docked stack.
                 // We get the bounds to use from window manager which has been adjusted for any
                 // screen controls and is also the same for all stacks.
+                final Rect tempOtherTaskRect = new Rect();
+                final Rect tempOtherTaskInsetRect = new Rect();
                 for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
                     final ActivityStack current = getStack(i);
                     if (current != null && StackId.isResizeableByDockedStack(i)) {
-                        current.getStackDockedModeBounds(tempRect, true /* ignoreVisibility */);
-                        resizeStackLocked(i, tempRect, tempOtherTaskBounds,
-                                tempOtherTaskInsetBounds, preserveWindows,
-                                true /* allowResizeInDockedMode */, deferResume);
+                        current.getStackDockedModeBounds(tempRect, tempOtherTaskRect,
+                                tempOtherTaskInsetRect, true /* ignoreVisibility */);
+                        resizeStackLocked(i, tempRect,
+                                !tempOtherTaskRect.isEmpty() ? tempOtherTaskRect :
+                                        tempOtherTaskBounds,
+                                !tempOtherTaskInsetRect.isEmpty() ? tempOtherTaskInsetRect :
+                                        tempOtherTaskInsetBounds,
+                                preserveWindows, true /* allowResizeInDockedMode */, deferResume);
                     }
                 }
             }
@@ -2365,12 +2368,6 @@
             mWindowManager.continueSurfaceLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
-
-        mResizeDockedStackTimeout.notifyResizing(dockedBounds,
-                tempDockedTaskBounds != null
-                || tempDockedTaskInsetBounds != null
-                || tempOtherTaskBounds != null
-                || tempOtherTaskInsetBounds != null);
     }
 
     void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
@@ -4902,4 +4899,24 @@
         }
         return topActivityTokens;
     }
+
+    public IBinder getTopVisibleActivity(int uid) {
+        // TODO(b/33197203): get rid of DEFAULT_DISPLAY here?. Used in
+        // VoiceInteractionManagerServiceImpl#showSessionLocked.
+        final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
+        if (display == null) {
+            return null;
+        }
+        final ArrayList<ActivityStack> stacks = display.mStacks;
+        for (int i = stacks.size() - 1; i >= 0; i--) {
+            ActivityStack stack = stacks.get(i);
+            if (stack.getStackVisibilityLocked(null) == ActivityStack.STACK_VISIBLE) {
+                ActivityRecord top = stack.topActivity();
+                if (top != null && stack == mFocusedStack && top.app.uid == uid) {
+                    return top.appToken;
+                }
+            }
+        }
+        return null;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index f7eb5d5..96f732e 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -2072,6 +2072,9 @@
             case RECENTS_STACK_ID:
                 return r.isRecentsActivity();
             default:
+                if (StackId.isDynamicStack(stackId)) {
+                    return true;
+                }
                 Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId);
                 return false;
         }
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index b0a4746..2bd119e 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -280,7 +280,7 @@
     /**
      * @return true if Keyguard can be currently dismissed without entering credentials.
      */
-    private boolean canDismissKeyguard() {
+    boolean canDismissKeyguard() {
         return mWindowManager.isKeyguardTrusted() || !mWindowManager.isKeyguardSecure();
     }
 
diff --git a/services/core/java/com/android/server/am/ResizeDockedStackTimeout.java b/services/core/java/com/android/server/am/ResizeDockedStackTimeout.java
deleted file mode 100644
index ff39589..0000000
--- a/services/core/java/com/android/server/am/ResizeDockedStackTimeout.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.server.am;
-
-import android.graphics.Rect;
-import android.os.Handler;
-
-import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
-
-/**
- * When resizing the docked stack, a caller can temporarily supply task bounds that are different
- * from the stack bounds. In order to return to a sane state if the caller crashes or has a bug,
- * this class manages this cycle.
- */
-class ResizeDockedStackTimeout {
-
-    private static final long TIMEOUT_MS = 10 * 1000;
-    private final ActivityManagerService mService;
-    private final ActivityStackSupervisor mSupervisor;
-    private final Handler mHandler;
-    private final Rect mCurrentDockedBounds = new Rect();
-
-    private final Runnable mTimeoutRunnable = new Runnable() {
-        @Override
-        public void run() {
-            synchronized (mService) {
-                mSupervisor.resizeDockedStackLocked(mCurrentDockedBounds, null, null, null, null,
-                        PRESERVE_WINDOWS);
-            }
-        }
-    };
-
-    ResizeDockedStackTimeout(ActivityManagerService service, ActivityStackSupervisor supervisor,
-            Handler handler) {
-        mService = service;
-        mSupervisor = supervisor;
-        mHandler = handler;
-    }
-
-    void notifyResizing(Rect dockedBounds, boolean hasTempBounds) {
-        mHandler.removeCallbacks(mTimeoutRunnable);
-        if (!hasTempBounds) {
-            return;
-        }
-        mCurrentDockedBounds.set(dockedBounds);
-        mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
-    }
-
-}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index f12d7b7..fef4073 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1028,8 +1028,10 @@
         addActivityAtIndex(mActivities.size(), r);
     }
 
-    // TODO: Figure-out if any of the call points expect the window container to be reparented and
-    // correct them to use the right method.
+    /**
+     * Adds an activity {@param r} at the given {@param index}. The activity {@param r} must either
+     * be in the current task or unparented to any task.
+     */
     void addActivityAtIndex(int index, ActivityRecord r) {
         // Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
         if (!mActivities.remove(r) && r.fullscreen) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 31ef94f..213041e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -144,7 +144,6 @@
  */
 public class AudioService extends IAudioService.Stub
         implements AccessibilityManager.TouchExplorationStateChangeListener,
-            AccessibilityManager.AccessibilityStateChangeListener,
             AccessibilityManager.AccessibilityServicesStateChangeListener {
 
     private static final String TAG = "AudioService";
@@ -5926,25 +5925,13 @@
     //==========================================================================================
     // Accessibility
 
-    /**
-     * Compile-time constant to enable the use of an independent a11y volume:
-     * - set to true to listen to a11y services state changes and read
-     *   the whether any exposes the FLAG_ENABLE_ACCESSIBILITY_VOLUME flag
-     * - set to false to listen to when accessibility services are started (e.g. "TalkBack started")
-     */
-    private static final boolean USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME = false;
-
     private void initA11yMonitoring() {
         final AccessibilityManager accessibilityManager =
                 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
         updateDefaultStreamOverrideDelay(accessibilityManager.isTouchExplorationEnabled());
         updateA11yVolumeAlias(accessibilityManager.isEnabled());
         accessibilityManager.addTouchExplorationStateChangeListener(this);
-        if (USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME) {
-            accessibilityManager.addAccessibilityServicesStateChangeListener(this);
-        } else {
-            accessibilityManager.addAccessibilityStateChangeListener(this);
-        }
+        accessibilityManager.addAccessibilityServicesStateChangeListener(this);
     }
 
     //---------------------------------------------------------------------------------
@@ -5982,12 +5969,6 @@
 
     private static boolean sIndependentA11yVolume = false;
 
-    // implementation of AccessibilityStateChangeListener
-    @Override
-    public void onAccessibilityStateChanged(boolean enabled) {
-        updateA11yVolumeAlias(enabled);
-    }
-
     // implementation of AccessibilityServicesStateChangeListener
     @Override
     public void onAccessibilityServicesStateChanged() {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index b0e4509..9d63462 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1244,10 +1244,6 @@
                 return false;
             }
 
-            private boolean isSimCardAbsent(String state) {
-                return IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(state);
-            }
-
             private boolean isSimCardLoaded(String state) {
                 return IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state);
             }
@@ -1264,9 +1260,8 @@
                 // used to verify this receiver is still current
                 final private int mGenerationNumber;
 
-                // we're interested in edge-triggered LOADED notifications, so
-                // ignore LOADED unless we saw an ABSENT state first
-                private boolean mSimAbsentSeen = false;
+                // used to check the sim state transition from non-loaded to loaded
+                private boolean mSimNotLoadedSeen = false;
 
                 public SimChangeBroadcastReceiver(int generationNumber) {
                     mGenerationNumber = generationNumber;
@@ -1284,16 +1279,16 @@
 
                     final String state = intent.getStringExtra(
                             IccCardConstants.INTENT_KEY_ICC_STATE);
-                    Log.d(TAG, "got Sim changed to state " + state + ", mSimAbsentSeen=" +
-                            mSimAbsentSeen);
+                    Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
+                            mSimNotLoadedSeen);
 
-                    if (isSimCardAbsent(state)) {
-                        if (!mSimAbsentSeen) mSimAbsentSeen = true;
+                    if (!isSimCardLoaded(state)) {
+                        if (!mSimNotLoadedSeen) mSimNotLoadedSeen = true;
                         return;
                     }
 
-                    if (isSimCardLoaded(state) && mSimAbsentSeen) {
-                        mSimAbsentSeen = false;
+                    if (isSimCardLoaded(state) && mSimNotLoadedSeen) {
+                        mSimNotLoadedSeen = false;
 
                         if (!hasMobileHotspotProvisionApp()) return;
 
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 23481dc..08a3332 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -40,7 +40,9 @@
  * pertaining to the current and any potential upstream network.
  *
  * Calling #start() registers two callbacks: one to track the system default
- * network and a second to specifically observe TYPE_MOBILE_DUN networks.
+ * network and a second to observe all networks.  The latter is necessary
+ * while the expression of preferred upstreams remains a list of legacy
+ * connectivity types.  In future, this can be revisited.
  *
  * The methods and data members of this class are only to be accessed and
  * modified from the tethering master state machine thread. Any other
@@ -48,6 +50,10 @@
  *
  * TODO: Move upstream selection logic here.
  *
+ * All callback methods are run on the same thread as the specified target
+ * state machine.  This class does not require locking when accessed from this
+ * thread.  Access from other threads is not advised.
+ *
  * @hide
  */
 public class UpstreamNetworkMonitor {
@@ -60,15 +66,20 @@
     public static final int EVENT_ON_LINKPROPERTIES = 3;
     public static final int EVENT_ON_LOST           = 4;
 
+    private static final int LISTEN_ALL = 1;
+    private static final int TRACK_DEFAULT = 2;
+    private static final int MOBILE_REQUEST = 3;
+
     private final Context mContext;
     private final StateMachine mTarget;
     private final int mWhat;
     private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
     private ConnectivityManager mCM;
+    private NetworkCallback mListenAllCallback;
     private NetworkCallback mDefaultNetworkCallback;
-    private NetworkCallback mDunTetheringCallback;
     private NetworkCallback mMobileNetworkCallback;
     private boolean mDunRequired;
+    private Network mCurrentDefault;
 
     public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
         mContext = ctx;
@@ -85,16 +96,13 @@
     public void start() {
         stop();
 
-        mDefaultNetworkCallback = new UpstreamNetworkCallback();
-        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
+        final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
+                .clearCapabilities().build();
+        mListenAllCallback = new UpstreamNetworkCallback(LISTEN_ALL);
+        cm().registerNetworkCallback(listenAllRequest, mListenAllCallback);
 
-        final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                .build();
-        mDunTetheringCallback = new UpstreamNetworkCallback();
-        cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback);
+        mDefaultNetworkCallback = new UpstreamNetworkCallback(TRACK_DEFAULT);
+        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
     }
 
     public void stop() {
@@ -103,8 +111,8 @@
         releaseCallback(mDefaultNetworkCallback);
         mDefaultNetworkCallback = null;
 
-        releaseCallback(mDunTetheringCallback);
-        mDunTetheringCallback = null;
+        releaseCallback(mListenAllCallback);
+        mListenAllCallback = null;
 
         mNetworkMap.clear();
     }
@@ -140,7 +148,7 @@
 
         // The existing default network and DUN callbacks will be notified.
         // Therefore, to avoid duplicate notifications, we only register a no-op.
-        mMobileNetworkCallback = new NetworkCallback();
+        mMobileNetworkCallback = new UpstreamNetworkCallback(MOBILE_REQUEST);
 
         // TODO: Change the timeout from 0 (no onUnavailable callback) to some
         // moderate callback timeout. This might be useful for updating some UI.
@@ -165,86 +173,117 @@
         return (network != null) ? mNetworkMap.get(network) : null;
     }
 
-    private void handleAvailable(Network network) {
-        if (VDBG) {
-            Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
-        }
+    private void handleAvailable(int callbackType, Network network) {
+        if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
+
         if (!mNetworkMap.containsKey(network)) {
             mNetworkMap.put(network,
                     new NetworkState(null, null, null, network, null, null));
         }
 
-        final ConnectivityManager cm = cm();
-
-        if (mDefaultNetworkCallback != null) {
+        // Always request whatever extra information we can, in case this
+        // was already up when start() was called, in which case we would
+        // not have been notified of any information that had not changed.
+        final NetworkCallback cb =
+                (callbackType == TRACK_DEFAULT) ? mDefaultNetworkCallback :
+                (callbackType == MOBILE_REQUEST) ? mMobileNetworkCallback : null;
+        if (cb != null) {
+            final ConnectivityManager cm = cm();
             cm.requestNetworkCapabilities(mDefaultNetworkCallback);
             cm.requestLinkProperties(mDefaultNetworkCallback);
         }
 
-        // Requesting updates for mDunTetheringCallback is not
-        // necessary. Because it's a listen, it will already have
-        // heard all NetworkCapabilities and LinkProperties updates
-        // since UpstreamNetworkMonitor was started. Because we
-        // start UpstreamNetworkMonitor before chooseUpstreamType()
-        // is ever invoked (it can register a DUN request) this is
-        // mostly safe. However, if a DUN network is already up for
-        // some reason (unlikely, because DUN is restricted and,
-        // unless the DUN network is shared with another APN, only
-        // the system can request it and this is the only part of
-        // the system that requests it) we won't know its
-        // LinkProperties or NetworkCapabilities.
+        if (callbackType == TRACK_DEFAULT) {
+            mCurrentDefault = network;
+        }
 
+        // Requesting updates for mListenAllCallback is not currently possible
+        // because it's a "listen". Two possible solutions to getting updates
+        // about networks without waiting for a change (which might never come)
+        // are:
+        //
+        //     [1] extend request{NetworkCapabilities,LinkProperties}() to
+        //         take a Network argument and have ConnectivityService do
+        //         what's required (if the network satisfies the request)
+        //
+        //     [2] explicitly file a NetworkRequest for each connectivity type
+        //         listed as a preferred upstream and wait for these callbacks
+        //         to be notified (requires tracking many more callbacks).
+        //
+        // Until this is addressed, networks that exist prior to the "listen"
+        // registration and which do not subsequently change will not cause
+        // us to learn their NetworkCapabilities nor their LinkProperties.
+
+        // TODO: If sufficient information is available to select a more
+        // preferable upstream, do so now and notify the target.
         notifyTarget(EVENT_ON_AVAILABLE, network);
     }
 
     private void handleNetCap(Network network, NetworkCapabilities newNc) {
-        if (!mNetworkMap.containsKey(network)) {
-            // Ignore updates for networks for which we have not yet
-            // received onAvailable() - which should never happen -
-            // or for which we have already received onLost().
+        final NetworkState prev = mNetworkMap.get(network);
+        if (prev == null || newNc.equals(prev.networkCapabilities)) {
+            // Ignore notifications about networks for which we have not yet
+            // received onAvailable() (should never happen) and any duplicate
+            // notifications (e.g. matching more than one of our callbacks).
             return;
         }
+
         if (VDBG) {
             Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
                     network, newNc));
         }
 
-        final NetworkState prev = mNetworkMap.get(network);
-        mNetworkMap.put(network,
-                new NetworkState(null, prev.linkProperties, newNc,
-                                 network, null, null));
+        mNetworkMap.put(network, new NetworkState(
+                null, prev.linkProperties, newNc, network, null, null));
+        // TODO: If sufficient information is available to select a more
+        // preferable upstream, do so now and notify the target.
         notifyTarget(EVENT_ON_CAPABILITIES, network);
     }
 
     private void handleLinkProp(Network network, LinkProperties newLp) {
-        if (!mNetworkMap.containsKey(network)) {
-            // Ignore updates for networks for which we have not yet
-            // received onAvailable() - which should never happen -
-            // or for which we have already received onLost().
+        final NetworkState prev = mNetworkMap.get(network);
+        if (prev == null || newLp.equals(prev.linkProperties)) {
+            // Ignore notifications about networks for which we have not yet
+            // received onAvailable() (should never happen) and any duplicate
+            // notifications (e.g. matching more than one of our callbacks).
             return;
         }
+
         if (VDBG) {
             Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
                     network, newLp));
         }
 
-        final NetworkState prev = mNetworkMap.get(network);
-        mNetworkMap.put(network,
-                new NetworkState(null, newLp, prev.networkCapabilities,
-                                 network, null, null));
+        mNetworkMap.put(network, new NetworkState(
+                null, newLp, prev.networkCapabilities, network, null, null));
+        // TODO: If sufficient information is available to select a more
+        // preferable upstream, do so now and notify the target.
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
     }
 
-    private void handleLost(Network network) {
-        if (!mNetworkMap.containsKey(network)) {
-            // Ignore updates for networks for which we have not yet
-            // received onAvailable() - which should never happen -
-            // or for which we have already received onLost().
+    private void handleLost(int callbackType, Network network) {
+        if (callbackType == TRACK_DEFAULT) {
+            mCurrentDefault = null;
+            // Receiving onLost() for a default network does not necessarily
+            // mean the network is gone.  We wait for a separate notification
+            // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
+            // clearing all state.
             return;
         }
-        if (VDBG) {
-            Log.d(TAG, "EVENT_ON_LOST for " + network);
+
+        if (!mNetworkMap.containsKey(network)) {
+            // Ignore loss of networks about which we had not previously
+            // learned any information or for which we have already processed
+            // an onLost() notification.
+            return;
         }
+
+        if (VDBG) Log.d(TAG, "EVENT_ON_LOST for " + network);
+
+        // TODO: If sufficient information is available to select a more
+        // preferable upstream, do so now and notify the target.  Likewise,
+        // if the current upstream network is gone, notify the target of the
+        // fact that we now have no upstream at all.
         notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
     }
 
@@ -261,9 +300,15 @@
      * tethering master state machine thread for subsequent processing.
      */
     private class UpstreamNetworkCallback extends NetworkCallback {
+        private final int mCallbackType;
+
+        UpstreamNetworkCallback(int callbackType) {
+            mCallbackType = callbackType;
+        }
+
         @Override
         public void onAvailable(Network network) {
-            mTarget.getHandler().post(() -> handleAvailable(network));
+            mTarget.getHandler().post(() -> handleAvailable(mCallbackType, network));
         }
 
         @Override
@@ -278,7 +323,7 @@
 
         @Override
         public void onLost(Network network) {
-            mTarget.getHandler().post(() -> handleLost(network));
+            mTarget.getHandler().post(() -> handleLost(mCallbackType, network));
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 6719182..4a52b3c 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -93,6 +93,11 @@
     public static final int FLAG_ROUND = 1 << 8;
 
     /**
+     * Flag: This display can show its content when non-secure keyguard is shown.
+     */
+    public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 9;
+
+    /**
      * Touch attachment: Display does not receive touch.
      */
     public static final int TOUCH_NONE = 0;
@@ -420,6 +425,9 @@
         if ((flags & FLAG_ROUND) != 0) {
             msg.append(", FLAG_ROUND");
         }
+        if ((flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
+            msg.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD");
+        }
         return msg.toString();
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index cd07793..fd89b97 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -16,13 +16,19 @@
 
 package com.android.server.display;
 
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+import static android.hardware.display.DisplayManager
+        .VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
+
 import com.android.internal.util.IndentingPrintWriter;
 
 import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.SensorManager;
-import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayViewport;
@@ -1446,11 +1452,17 @@
                 throw new IllegalArgumentException("Surface can't be single-buffered");
             }
 
-            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
-                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+            if ((flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
+                flags |= VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+
+                // Public displays can't be allowed to show content when locked.
+                if ((flags & VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
+                    throw new IllegalArgumentException(
+                            "Public display must not be marked as SHOW_WHEN_LOCKED_INSECURE");
+                }
             }
-            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
-                flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+            if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
+                flags &= ~VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
             }
 
             if (projection != null) {
@@ -1465,7 +1477,7 @@
             }
 
             if (callingUid != Process.SYSTEM_UID &&
-                    (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
+                    (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
                 if (!canProjectVideo(projection)) {
                     throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
                             + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
@@ -1473,7 +1485,7 @@
                             + "display.");
                 }
             }
-            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
+            if ((flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
                 if (!canProjectSecureVideo(projection)) {
                     throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
                             + "or an appropriate MediaProjection token to create a "
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 287a25a..40a8952 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -223,6 +223,9 @@
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ROUND) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_ROUND;
             }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
+            }
             mBaseDisplayInfo.type = deviceInfo.type;
             mBaseDisplayInfo.address = deviceInfo.address;
             mBaseDisplayInfo.name = deviceInfo.name;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 9d0fde5..74e025d 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -16,8 +16,14 @@
 
 package com.android.server.display;
 
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+import static android.hardware.display.DisplayManager
+        .VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+
 import android.content.Context;
-import android.hardware.display.DisplayManager;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
@@ -63,7 +69,7 @@
     public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback,
             IMediaProjection projection, int ownerUid, String ownerPackageName,
             String name, int width, int height, int densityDpi, Surface surface, int flags) {
-        boolean secure = (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
+        boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
         IBinder appToken = callback.asBinder();
         IBinder displayToken = SurfaceControl.createDisplay(name, secure);
         final String baseUniqueId =
@@ -308,23 +314,23 @@
                 mInfo.yDpi = mDensityDpi;
                 mInfo.presentationDeadlineNanos = 1000000000L / (int) REFRESH_RATE; // 1 frame
                 mInfo.flags = 0;
-                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
                             | DisplayDeviceInfo.FLAG_NEVER_BLANK;
                 }
-                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
                     mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK;
                 } else {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
                 }
 
-                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
                 }
-                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
 
-                    if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
+                    if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
                         // For demonstration purposes, allow rotation of the external display.
                         // In the future we might allow the user to configure this directly.
                         if ("portrait".equals(SystemProperties.get(
@@ -333,6 +339,9 @@
                         }
                     }
                 }
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
+                }
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
                 mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 218b571..60df2c4 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -86,6 +86,7 @@
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.IRingtonePlayer;
+import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -132,6 +133,8 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
@@ -549,6 +552,9 @@
                     return;
                 }
                 final long now = System.currentTimeMillis();
+                MetricsLogger.action(r.getLogMaker(now)
+                        .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+                        .setType(MetricsEvent.TYPE_ACTION));
                 EventLogTags.writeNotificationClicked(key,
                         r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
 
@@ -570,6 +576,10 @@
                     return;
                 }
                 final long now = System.currentTimeMillis();
+                MetricsLogger.action(r.getLogMaker(now)
+                        .setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION)
+                        .setType(MetricsEvent.TYPE_ACTION)
+                        .setSubtype(actionIndex));
                 EventLogTags.writeNotificationActionClicked(key, actionIndex,
                         r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
                 // TODO: Log action click via UsageStats.
@@ -586,6 +596,8 @@
 
         @Override
         public void onPanelRevealed(boolean clearEffects, int items) {
+            MetricsLogger.visible(getContext(), MetricsEvent.NOTIFICATION_PANEL);
+            MetricsLogger.histogram(getContext(), "notification_load", items);
             EventLogTags.writeNotificationPanelRevealed(items);
             if (clearEffects) {
                 clearEffects();
@@ -594,6 +606,7 @@
 
         @Override
         public void onPanelHidden() {
+            MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL);
             EventLogTags.writeNotificationPanelHidden();
         }
 
@@ -655,6 +668,9 @@
                 if (r != null) {
                     r.stats.onExpansionChanged(userAction, expanded);
                     final long now = System.currentTimeMillis();
+                    MetricsLogger.action(r.getLogMaker(now)
+                            .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+                            .setType(MetricsEvent.TYPE_DETAIL));
                     EventLogTags.writeNotificationExpansion(key,
                             userAction ? 1 : 0, expanded ? 1 : 0,
                             r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
@@ -3426,6 +3442,10 @@
                     & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) {
                 if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on");
             } else {
+                MetricsLogger.action(record.getLogMaker()
+                        .setCategory(MetricsEvent.NOTIFICATION_ALERT)
+                        .setType(MetricsEvent.TYPE_OPEN)
+                        .setSubtype((buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)));
                 EventLogTags.writeNotificationAlert(key,
                         buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
                 mHandler.post(mBuzzBeepBlinked);
@@ -3896,6 +3916,10 @@
         mArchive.record(r.sbn);
 
         final long now = System.currentTimeMillis();
+        MetricsLogger.action(r.getLogMaker(now)
+                .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+                .setType(MetricsEvent.TYPE_DISMISS)
+                .setSubtype(reason));
         EventLogTags.writeNotificationCanceled(canceledKey, reason,
                 r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
     }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 8998128..5739693 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -31,6 +31,7 @@
 import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
 import android.media.AudioSystem;
+import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Build;
 import android.os.UserHandle;
@@ -44,6 +45,8 @@
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
@@ -118,6 +121,7 @@
     private ArrayList<String> mPeopleOverride;
     private ArrayList<SnoozeCriterion> mSnoozeCriteria;
     private boolean mShowBadge;
+    private LogMaker mLogMaker;
 
     @VisibleForTesting
     public NotificationRecord(Context context, StatusBarNotification sbn,
@@ -585,9 +589,16 @@
         final long now = System.currentTimeMillis();
         mVisibleSinceMs = visible ? now : mVisibleSinceMs;
         stats.onVisibilityChanged(visible);
+        MetricsLogger.action(getLogMaker(now)
+                .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+                .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
+                .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank));
+        if (visible) {
+            MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
+        }
         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
-                (int) (now - mCreationTimeMs),
-                (int) (now - mUpdateTimeMs),
+                getLifespanMs(now),
+                getFreshnessMs(now),
                 0, // exposure time
                 rank);
     }
@@ -690,4 +701,25 @@
     protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) {
         mSnoozeCriteria = snoozeCriteria;
     }
+
+    public LogMaker getLogMaker(long now) {
+        if (mLogMaker == null) {
+            mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
+                    .setPackageName(sbn.getPackageName())
+                    .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId())
+                    .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag());
+        }
+        return mLogMaker
+                .setCategory(MetricsEvent.VIEW_UNKNOWN)
+                .setType(MetricsEvent.TYPE_UNKNOWN)
+                .setSubtype(0)
+                .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX)
+                .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
+                .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
+                .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now));
+    }
+
+    public LogMaker getLogMaker() {
+        return getLogMaker(System.currentTimeMillis());
+    }
 }
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 2e35e3d..6a00722 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -19,6 +19,8 @@
 import android.app.NotificationChannelGroup;
 import android.content.pm.ParceledListSlice;
 
+import java.util.Collection;
+
 public interface RankingConfig {
 
     void setImportance(String packageName, int uid, int importance);
@@ -26,6 +28,8 @@
     void setShowBadge(String packageName, int uid, boolean showBadge);
     boolean canShowBadge(String packageName, int uid);
 
+    Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+            int uid);
     void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
             boolean fromTargetApp);
     ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 01cca2d0..5b6ac69 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -47,6 +47,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -691,7 +692,11 @@
                 }
             }
         }
-        groups.addAll(r.groups.values());
+        for (NotificationChannelGroup group : r.groups.values()) {
+            if (group.getChannels().size() > 0) {
+                groups.add(group);
+            }
+        }
         if (nonGrouped.getChannels().size() > 0) {
             groups.add(nonGrouped);
         }
@@ -699,6 +704,17 @@
     }
 
     @Override
+    @VisibleForTesting
+    public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+            int uid) {
+        Record r = getRecord(pkg, uid);
+        if (r == null) {
+            return new ArrayList<>();
+        }
+        return r.groups.values();
+    }
+
+    @Override
     public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
             boolean includeDeleted) {
         Preconditions.checkNotNull(pkg);
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 5dd651f..a30e0639 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -46,7 +46,6 @@
 import java.util.concurrent.TimeUnit;
 
 import android.os.SystemClock;
-import com.android.internal.logging.MetricsLogger;
 
 /**
  * This {@link NotificationSignalExtractor} attempts to validate
@@ -264,9 +263,6 @@
         // record the best available data, so far:
         affinityOut[0] = affinity;
 
-        MetricsLogger.histogram(mBaseContext, "validate_people_cache_latency",
-                (int) (SystemClock.elapsedRealtime() - start));
-
         if (pendingLookups.isEmpty()) {
             if (VERBOSE) Slog.i(TAG, "final affinity: " + affinity);
             return null;
@@ -485,9 +481,6 @@
                 mUsageStats.registerPeopleAffinity(mRecord, mContactAffinity > NONE,
                         mContactAffinity == STARRED_CONTACT, false /* cached */);
             }
-
-            MetricsLogger.histogram(mBaseContext, "validate_people_lookup_latency",
-                    (int) (SystemClock.elapsedRealtime() - start));
         }
 
         @Override
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
new file mode 100644
index 0000000..04d91f8
--- /dev/null
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -0,0 +1,117 @@
+/*
+ * 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.server.om;
+
+import static com.android.server.om.OverlayManagerService.DEBUG;
+import static com.android.server.om.OverlayManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.content.om.OverlayInfo;
+import android.content.pm.PackageInfo;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.Installer;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * Handle the creation and deletion of idmap files.
+ *
+ * The actual work is performed by the idmap binary, launched through Installer
+ * and installd.
+ *
+ * Note: this class is subclassed in the OMS unit tests, and hence not marked as final.
+ */
+class IdmapManager {
+    private final Installer mInstaller;
+
+    IdmapManager(final Installer installer) {
+        mInstaller = installer;
+    }
+
+    boolean createIdmap(@NonNull final PackageInfo targetPackage,
+            @NonNull final PackageInfo overlayPackage, int userId) {
+        // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
+        if (DEBUG) {
+            Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and "
+                    + overlayPackage.packageName);
+        }
+        final int sharedGid = UserHandle.getSharedAppGid(targetPackage.applicationInfo.uid);
+        final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
+        final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
+        try {
+            mInstaller.idmap(targetPath, overlayPath, sharedGid);
+        } catch (InstallerException e) {
+            Slog.w(TAG, "failed to generate idmap for " + targetPath + " and "
+                    + overlayPath + ": " + e.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) {
+        // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
+        if (DEBUG) {
+            Slog.d(TAG, "remove idmap for " + oi.baseCodePath);
+        }
+        try {
+            mInstaller.removeIdmap(oi.baseCodePath);
+        } catch (InstallerException e) {
+            Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath + ": " + e.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    boolean idmapExists(@NonNull final OverlayInfo oi) {
+        // unused OverlayInfo.userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
+        return new File(getIdmapPath(oi.baseCodePath)).isFile();
+    }
+
+    boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
+        // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
+        return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath())).isFile();
+    }
+
+    boolean isDangerous(@NonNull final PackageInfo overlayPackage, final int userId) {
+        // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible
+        return isDangerous(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath()));
+    }
+
+    private String getIdmapPath(@NonNull final String baseCodePath) {
+        final StringBuilder sb = new StringBuilder("/data/resource-cache/");
+        sb.append(baseCodePath.substring(1).replace('/', '@'));
+        sb.append("@idmap");
+        return sb.toString();
+    }
+
+    private boolean isDangerous(@NonNull final String idmapPath) {
+        try (DataInputStream dis = new DataInputStream(new FileInputStream(idmapPath))) {
+            final int magic = dis.readInt();
+            final int version = dis.readInt();
+            final int dangerous = dis.readInt();
+            return dangerous != 0;
+        } catch (IOException e) {
+            return true;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
new file mode 100644
index 0000000..cc709ce
--- /dev/null
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -0,0 +1,850 @@
+/*
+ * 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.server.om;
+
+import static android.app.AppGlobals.getPackageManager;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_CHANGED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.pm.PackageManager.SIGNATURE_MATCH;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.om.IOverlayManager;
+import android.content.om.OverlayInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.server.FgThread;
+import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.pm.Installer;
+import com.android.server.pm.UserManagerService;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Service to manage asset overlays.
+ *
+ * <p>Asset overlays are additional resources that come from apks loaded
+ * alongside the system and app apks. This service, the OverlayManagerService
+ * (OMS), tracks which installed overlays to use and provides methods to change
+ * this. Changes propagate to running applications as part of the Activity
+ * lifecycle. This allows Activities to reread their resources at a well
+ * defined point.</p>
+ *
+ * <p>By itself, the OMS will not change what overlays should be active.
+ * Instead, it is only responsible for making sure that overlays *can* be used
+ * from a technical and security point of view and to activate overlays in
+ * response to external requests. The responsibility to toggle overlays on and
+ * off lies within components that implement different use-cases such as themes
+ * or dynamic customization.</p>
+ *
+ * <p>The OMS receives input from three sources:</p>
+ *
+ * <ul>
+ *     <li>Callbacks from the SystemService class, specifically when the
+ *     Android framework is booting and when the end user switches Android
+ *     users.</li>
+ *
+ *     <li>Intents from the PackageManagerService (PMS). Overlays are regular
+ *     apks, and whenever a package is installed (or removed, or has a
+ *     component enabled or disabled), the PMS broadcasts this as an intent.
+ *     When the OMS receives one of these intents, it updates its internal
+ *     representation of the available overlays and, if there was a visible
+ *     change, triggers an asset refresh in the affected apps.</li>
+ *
+ *     <li>External requests via the {@link IOverlayManager AIDL interface}.
+ *     The interface allows clients to read information about the currently
+ *     available overlays, change whether an overlay should be used or not, and
+ *     change the relative order in which overlay packages are loaded.
+ *     Read-access is granted if the request targets the same Android user as
+ *     the caller runs as, or if the caller holds the
+ *     INTERACT_ACROSS_USERS_FULL permission. Write-access is granted if the
+ *     caller is granted read-access and additionaly holds the
+ *     CHANGE_OVERLAY_PACKAGES permission.</li>
+ * </ul>
+ *
+ * <p>The AIDL interface works with String package names, int user IDs, and
+ * {@link OverlayInfo} objects. OverlayInfo instances are used to track a
+ * specific pair of target and overlay packages and include information such as
+ * the current state of the overlay. OverlayInfo objects are immutable.</p>
+ *
+ * <p>Internally, OverlayInfo objects are maintained by the
+ * OverlayManagerSettings class. The OMS and its helper classes are notified of
+ * changes to the settings by the OverlayManagerSettings.ChangeListener
+ * callback interface. The file /data/system/overlays.xml is used to persist
+ * the settings.</p>
+ *
+ * <p>Creation and deletion of idmap files are handled by the IdmapManager
+ * class.</p>
+ *
+ * <p>The following is an overview of OMS and its related classes. Note how box
+ * (2) does the heavy lifting, box (1) interacts with the Android framework,
+ * and box (3) replaces box (1) during unit testing.</p>
+ *
+ * <pre>
+ *         Android framework
+ *            |         ^
+ *      . . . | . . . . | . . . .
+ *     .      |         |       .
+ *     .    AIDL,   broadcasts  .
+ *     .   intents      |       .
+ *     .      |         |       . . . . . . . . . . . .
+ *     .      v         |       .                     .
+ *     .  OverlayManagerService . OverlayManagerTests .
+ *     .                  \     .     /               .
+ *     . (1)               \    .    /            (3) .
+ *      . . . . . . . . . . \ . . . / . . . . . . . . .
+ *     .                     \     /              .
+ *     . (2)                  \   /               .
+ *     .           OverlayManagerServiceImpl      .
+ *     .                  |            |          .
+ *     .                  |            |          .
+ *     . OverlayManagerSettings     IdmapManager  .
+ *     .                                          .
+ *     . . . .  . . . . . . . . . . . . . . . . . .
+ * </pre>
+ *
+ * <p>Finally, here is a list of keywords used in the OMS context.</p>
+ *
+ * <ul>
+ *     <li><b>target [package]</b> -- A regular apk that may have its resource
+ *     pool extended  by zero or more overlay packages.</li>
+ *
+ *     <li><b>overlay [package]</b> -- An apk that provides additional
+ *     resources to another apk.</li>
+ *
+ *     <li><b>OMS</b> -- The OverlayManagerService, i.e. this class.</li>
+ *
+ *     <li><b>approved</b> -- An overlay is approved if the OMS has verified
+ *     that it can be used technically speaking (its target package is
+ *     installed, at least one resource name in both packages match, the
+ *     idmap was created, etc) and that it is secure to do so. External
+ *     clients can not change this state.</li>
+ *
+ *     <li><b>not approved</b> -- The opposite of approved.</li>
+ *
+ *     <li><b>enabled</b> -- An overlay currently in active use and thus part
+ *     of resource lookups. This requires the overlay to be approved. Only
+ *     external clients can change this state.</li>
+ *
+ *     <li><b>disabled</b> -- The opposite of enabled.</li>
+ *
+ *     <li><b>idmap</b> -- A mapping of resource IDs between target and overlay
+ *     used during resource lookup. Also the name of the binary that creates
+ *     the mapping.</li>
+ * </ul>
+ */
+public final class OverlayManagerService extends SystemService {
+
+    static final String TAG = "OverlayManager";
+
+    static final boolean DEBUG = false;
+
+    static final String PERMISSION_DENIED = "Operation not permitted for user shell";
+
+    private final Object mLock = new Object();
+
+    private final AtomicFile mSettingsFile;
+
+    private final PackageManagerHelper mPackageManager;
+
+    private final UserManagerService mUserManager;
+
+    private final OverlayManagerSettings mSettings;
+
+    private final OverlayManagerServiceImpl mImpl;
+
+    private final AtomicBoolean mPersistSettingsScheduled = new AtomicBoolean(false);
+
+    public OverlayManagerService(@NonNull final Context context,
+            @NonNull final Installer installer) {
+        super(context);
+        mSettingsFile =
+            new AtomicFile(new File(Environment.getDataSystemDirectory(), "overlays.xml"));
+        mPackageManager = new PackageManagerHelper();
+        mUserManager = UserManagerService.getInstance();
+        IdmapManager im = new IdmapManager(installer);
+        mSettings = new OverlayManagerSettings();
+        mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings);
+
+        final IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(ACTION_PACKAGE_CHANGED);
+        packageFilter.addAction(ACTION_PACKAGE_REMOVED);
+        packageFilter.addDataScheme("package");
+        getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL,
+                packageFilter, null, null);
+
+        final IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(ACTION_USER_REMOVED);
+        getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL,
+                userFilter, null, null);
+
+        restoreSettings();
+        onSwitchUser(UserHandle.USER_SYSTEM);
+        schedulePersistSettings();
+
+        mSettings.addChangeListener(new OverlayChangeListener());
+
+        publishBinderService(Context.OVERLAY_SERVICE, mService);
+        publishLocalService(OverlayManagerService.class, this);
+    }
+
+    @Override
+    public void onStart() {
+        // Intentionally left empty.
+    }
+
+    @Override
+    public void onSwitchUser(final int newUserId) {
+        // ensure overlays in the settings are up-to-date, and propagate
+        // any asset changes to the rest of the system
+        final List<String> targets;
+        synchronized (mLock) {
+            targets = mImpl.onSwitchUser(newUserId);
+        }
+        updateAssets(newUserId, targets);
+    }
+
+    private final class PackageReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
+            final Uri data = intent.getData();
+            if (data == null) {
+                Slog.e(TAG, "Cannot handle package broadcast with null data");
+                return;
+            }
+            final String packageName = data.getSchemeSpecificPart();
+
+            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+
+            final int[] userIds;
+            final int extraUid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
+            if (extraUid == UserHandle.USER_NULL) {
+                userIds = mUserManager.getUserIds();
+            } else {
+                userIds = new int[] { UserHandle.getUserId(extraUid) };
+            }
+
+            switch (intent.getAction()) {
+                case ACTION_PACKAGE_ADDED:
+                    if (replacing) {
+                        onPackageUpgraded(packageName, userIds);
+                    } else {
+                        onPackageAdded(packageName, userIds);
+                    }
+                    break;
+                case ACTION_PACKAGE_CHANGED:
+                    onPackageChanged(packageName, userIds);
+                    break;
+                case ACTION_PACKAGE_REMOVED:
+                    if (replacing) {
+                        onPackageUpgrading(packageName, userIds);
+                    } else {
+                        onPackageRemoved(packageName, userIds);
+                    }
+                    break;
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void onPackageAdded(@NonNull final String packageName,
+                @NonNull final int[] userIds) {
+            for (final int userId : userIds) {
+                synchronized (mLock) {
+                    final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId, false);
+                    if (pi != null) {
+                        mPackageManager.cachePackageInfo(packageName, userId, pi);
+                        if (!isOverlayPackage(pi)) {
+                            mImpl.onTargetPackageAdded(packageName, userId);
+                        } else {
+                            mImpl.onOverlayPackageAdded(packageName, userId);
+                        }
+                    }
+                }
+            }
+        }
+
+        private void onPackageChanged(@NonNull final String packageName,
+                @NonNull final int[] userIds) {
+            for (int userId : userIds) {
+                synchronized (mLock) {
+                    final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId, false);
+                    if (pi != null) {
+                        mPackageManager.cachePackageInfo(packageName, userId, pi);
+                        if (!isOverlayPackage(pi)) {
+                            mImpl.onTargetPackageChanged(packageName, userId);
+                        } else {
+                            mImpl.onOverlayPackageChanged(packageName, userId);
+                        }
+                    }
+                }
+            }
+        }
+
+        private void onPackageUpgrading(@NonNull final String packageName,
+                @NonNull final int[] userIds) {
+            for (int userId : userIds) {
+                synchronized (mLock) {
+                    mPackageManager.forgetPackageInfo(packageName, userId);
+                    final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
+                    if (oi == null) {
+                        mImpl.onTargetPackageUpgrading(packageName, userId);
+                    } else {
+                        mImpl.onOverlayPackageUpgrading(packageName, userId);
+                    }
+                }
+            }
+        }
+
+        private void onPackageUpgraded(@NonNull final String packageName,
+                @NonNull final int[] userIds) {
+            for (int userId : userIds) {
+                synchronized (mLock) {
+                    final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId, false);
+                    if (pi != null) {
+                        mPackageManager.cachePackageInfo(packageName, userId, pi);
+                        if (!isOverlayPackage(pi)) {
+                            mImpl.onTargetPackageUpgraded(packageName, userId);
+                        } else {
+                            mImpl.onOverlayPackageUpgraded(packageName, userId);
+                        }
+                    }
+                }
+            }
+        }
+
+        private void onPackageRemoved(@NonNull final String packageName,
+                @NonNull final int[] userIds) {
+            for (int userId : userIds) {
+                synchronized (mLock) {
+                    mPackageManager.forgetPackageInfo(packageName, userId);
+                    final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
+                    if (oi == null) {
+                        mImpl.onTargetPackageRemoved(packageName, userId);
+                    } else {
+                        mImpl.onOverlayPackageRemoved(packageName, userId);
+                    }
+                }
+            }
+        }
+    }
+
+    private final class UserReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
+            switch (intent.getAction()) {
+                case ACTION_USER_REMOVED:
+                    final int userId =
+                            intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                    if (userId != UserHandle.USER_NULL) {
+                        synchronized (mLock) {
+                            mImpl.onUserRemoved(userId);
+                            mPackageManager.forgetAllPackageInfos(userId);
+                        }
+                    }
+                    break;
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+    }
+
+    private final IBinder mService = new IOverlayManager.Stub() {
+        @Override
+        public Map<String, List<OverlayInfo>> getAllOverlays(int userId)
+                throws RemoteException {
+            userId = handleIncomingUser(userId, "getAllOverlays");
+
+            synchronized (mLock) {
+                return mImpl.getOverlaysForUser(userId);
+            }
+        }
+
+        @Override
+        public List<OverlayInfo> getOverlayInfosForTarget(@Nullable final String targetPackageName,
+                int userId) throws RemoteException {
+            userId = handleIncomingUser(userId, "getOverlayInfosForTarget");
+            if (targetPackageName == null) {
+                return Collections.emptyList();
+            }
+
+            synchronized (mLock) {
+                return mImpl.getOverlayInfosForTarget(targetPackageName, userId);
+            }
+        }
+
+        @Override
+        public OverlayInfo getOverlayInfo(@Nullable final String packageName,
+                int userId) throws RemoteException {
+            userId = handleIncomingUser(userId, "getOverlayInfo");
+            if (packageName == null) {
+                return null;
+            }
+
+            synchronized (mLock) {
+                return mImpl.getOverlayInfo(packageName, userId);
+            }
+        }
+
+        @Override
+        public boolean setEnabled(@Nullable final String packageName, final boolean enable,
+                int userId) throws RemoteException {
+            enforceChangeOverlayPackagesPermission("setEnabled");
+            userId = handleIncomingUser(userId, "setEnabled");
+            if (packageName == null) {
+                return false;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    return mImpl.setEnabled(packageName, enable, userId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
+        public boolean setPriority(@Nullable final String packageName,
+                @Nullable final String parentPackageName, int userId) throws RemoteException {
+            enforceChangeOverlayPackagesPermission("setPriority");
+            userId = handleIncomingUser(userId, "setPriority");
+            if (packageName == null || parentPackageName == null) {
+                return false;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    return mImpl.setPriority(packageName, parentPackageName, userId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
+        public boolean setHighestPriority(@Nullable final String packageName, int userId)
+                throws RemoteException {
+            enforceChangeOverlayPackagesPermission("setHighestPriority");
+            userId = handleIncomingUser(userId, "setHighestPriority");
+            if (packageName == null) {
+                return false;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    return mImpl.setHighestPriority(packageName, userId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
+        public boolean setLowestPriority(@Nullable final String packageName, int userId)
+                throws RemoteException {
+            enforceChangeOverlayPackagesPermission("setLowestPriority");
+            userId = handleIncomingUser(userId, "setLowestPriority");
+            if (packageName == null) {
+                return false;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    return mImpl.setLowestPriority(packageName, userId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
+        public void onShellCommand(@NonNull final FileDescriptor in,
+                @NonNull final FileDescriptor out, @NonNull final FileDescriptor err,
+                @NonNull final String[] args, @NonNull final ShellCallback callback,
+                @NonNull final ResultReceiver resultReceiver) {
+            (new OverlayManagerShellCommand(this)).exec(
+                    this, in, out, err, args, callback, resultReceiver);
+        }
+
+        @Override
+        protected void dump(@NonNull final FileDescriptor fd, @NonNull final PrintWriter pw,
+                @NonNull final String[] argv) {
+            enforceDumpPermission("dump");
+
+            final boolean verbose = argv.length > 0 && "--verbose".equals(argv[0]);
+
+            synchronized (mLock) {
+                mImpl.onDump(pw);
+                mPackageManager.dump(pw, verbose);
+            }
+        }
+
+        /**
+         * Ensure that the caller has permission to interact with the given userId.
+         * If the calling user is not the same as the provided user, the caller needs
+         * to hold the INTERACT_ACROSS_USERS_FULL permission (or be system uid or
+         * root).
+         *
+         * @param userId the user to interact with
+         * @param message message for any SecurityException
+         */
+        private int handleIncomingUser(final int userId, @NonNull final String message) {
+            return ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, false, true, message, null);
+        }
+
+        /**
+         * Enforce that the caller holds the CHANGE_OVERLAY_PACKAGES permission (or is
+         * system or root).
+         *
+         * @param message used as message if SecurityException is thrown
+         * @throws SecurityException if the permission check fails
+         */
+        private void enforceChangeOverlayPackagesPermission(@NonNull final String message) {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, message);
+        }
+
+        /**
+         * Enforce that the caller holds the DUMP permission (or is system or root).
+         *
+         * @param message used as message if SecurityException is thrown
+         * @throws SecurityException if the permission check fails
+         */
+        private void enforceDumpPermission(@NonNull final String message) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
+                    message);
+        }
+    };
+
+    private boolean isOverlayPackage(@NonNull final PackageInfo pi) {
+        return pi != null && pi.overlayTarget != null;
+    }
+
+    private final class OverlayChangeListener implements OverlayManagerSettings.ChangeListener {
+        @Override
+        public void onSettingsChanged() {
+            schedulePersistSettings();
+        }
+
+        @Override
+        public void onOverlayAdded(@NonNull final OverlayInfo oi) {
+            scheduleBroadcast(Intent.ACTION_OVERLAY_ADDED, oi, oi.isEnabled());
+        }
+
+        @Override
+        public void onOverlayRemoved(@NonNull final OverlayInfo oi) {
+            scheduleBroadcast(Intent.ACTION_OVERLAY_REMOVED, oi, oi.isEnabled());
+        }
+
+        @Override
+        public void onOverlayChanged(@NonNull final OverlayInfo oi,
+                @NonNull final OverlayInfo oldOi) {
+            scheduleBroadcast(Intent.ACTION_OVERLAY_CHANGED, oi, oi.isEnabled() != oldOi.isEnabled());
+        }
+
+        @Override
+        public void onOverlayPriorityChanged(@NonNull final OverlayInfo oi) {
+            scheduleBroadcast(Intent.ACTION_OVERLAY_PRIORITY_CHANGED, oi, oi.isEnabled());
+        }
+
+        private void scheduleBroadcast(@NonNull final String action, @NonNull final OverlayInfo oi,
+                final boolean doUpdate) {
+            FgThread.getHandler().post(new BroadcastRunnable(action, oi, doUpdate));
+        }
+
+        private final class BroadcastRunnable implements Runnable {
+            private final String mAction;
+            private final OverlayInfo mOverlayInfo;
+            private final boolean mDoUpdate;
+
+            BroadcastRunnable(@NonNull final String action, @NonNull final OverlayInfo oi,
+                    final boolean doUpdate) {
+                mAction = action;
+                mOverlayInfo = oi;
+                mDoUpdate = doUpdate;
+            }
+
+            @Override
+            public void run() {
+                if (mDoUpdate) {
+                    updateAssets(mOverlayInfo.userId, mOverlayInfo.targetPackageName);
+                }
+                sendBroadcast(mAction, mOverlayInfo.targetPackageName, mOverlayInfo.packageName,
+                        mOverlayInfo.userId);
+            }
+
+            private void sendBroadcast(@NonNull final String action,
+                    @NonNull final String targetPackageName, @NonNull final String packageName,
+                    final int userId) {
+                final Intent intent = new Intent(action, Uri.fromParts("package",
+                            String.format("%s/%s", targetPackageName, packageName), null));
+                intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                if (DEBUG) {
+                    Slog.d(TAG, String.format("send broadcast %s", intent));
+                }
+                try {
+                    ActivityManagerNative.getDefault().broadcastIntent(null, intent, null, null, 0,
+                            null, null, null, android.app.AppOpsManager.OP_NONE, null, false, false,
+                            userId);
+                } catch (RemoteException e) {
+                    // Intentionally left empty.
+                }
+            }
+
+        }
+    }
+
+    private void updateAssets(final int userId, final String targetPackageName) {
+        final List<String> list = new ArrayList<>();
+        list.add(targetPackageName);
+        updateAssets(userId, list);
+    }
+
+    private void updateAssets(final int userId, List<String> targetPackageNames) {
+        // TODO: implement when we integrate OMS properly
+    }
+
+    private void schedulePersistSettings() {
+        if (mPersistSettingsScheduled.getAndSet(true)) {
+            return;
+        }
+        IoThread.getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                mPersistSettingsScheduled.set(false);
+                synchronized (mLock) {
+                    FileOutputStream stream = null;
+                    try {
+                        stream = mSettingsFile.startWrite();
+                        mSettings.persist(stream);
+                        mSettingsFile.finishWrite(stream);
+                    } catch (IOException | XmlPullParserException e) {
+                        mSettingsFile.failWrite(stream);
+                        Slog.e(TAG, "failed to persist overlay state", e);
+                    }
+                }
+            }
+        });
+    }
+
+    private void restoreSettings() {
+        synchronized (mLock) {
+            if (!mSettingsFile.getBaseFile().exists()) {
+                return;
+            }
+            try (final FileInputStream stream = mSettingsFile.openRead()) {
+                mSettings.restore(stream);
+
+                // We might have data for dying users if the device was
+                // restarted before we received USER_REMOVED. Remove data for
+                // users that will not exist after the system is ready.
+
+                final List<UserInfo> deadUsers = getDeadUsers();
+                final int N = deadUsers.size();
+                for (int i = 0; i < N; i++) {
+                    final UserInfo deadUser = deadUsers.get(i);
+                    final int userId = deadUser.getUserHandle().getIdentifier();
+                    mSettings.removeUser(userId);
+                }
+            } catch (IOException | XmlPullParserException e) {
+                Slog.e(TAG, "failed to restore overlay state", e);
+            }
+        }
+    }
+
+    private List<UserInfo> getDeadUsers() {
+        final List<UserInfo> users = mUserManager.getUsers(false);
+        final List<UserInfo> onlyLiveUsers = mUserManager.getUsers(true);
+        users.removeAll(onlyLiveUsers);
+        return users;
+    }
+
+    private static final class PackageManagerHelper implements
+            OverlayManagerServiceImpl.PackageManagerHelper {
+
+        private final IPackageManager mPackageManager;
+        private final PackageManagerInternal mPackageManagerInternal;
+
+        // Use a cache for performance and for consistency within OMS: because
+        // additional PACKAGE_* intents may be delivered while we process an
+        // intent, querying the PackageManagerService for the actual current
+        // state may lead to contradictions within OMS. Better then to lag
+        // behind until all pending intents have been processed.
+        private final SparseArray<HashMap<String, PackageInfo>> mCache = new SparseArray<>();
+
+        PackageManagerHelper() {
+            mPackageManager = getPackageManager();
+            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        }
+
+        public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
+                final boolean useCache) {
+            if (useCache) {
+                final PackageInfo cachedPi = getCachedPackageInfo(packageName, userId);
+                if (cachedPi != null) {
+                    return cachedPi;
+                }
+            }
+            try {
+                final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0, userId);
+                if (useCache && pi != null) {
+                    cachePackageInfo(packageName, userId, pi);
+                }
+                return pi;
+            } catch (RemoteException e) {
+                // Intentionally left empty.
+            }
+            return null;
+        }
+
+        @Override
+        public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
+            return getPackageInfo(packageName, userId, true);
+        }
+
+        @Override
+        public boolean signaturesMatching(@NonNull final String packageName1,
+                @NonNull final String packageName2, final int userId) {
+            // The package manager does not support different versions of packages
+            // to be installed for different users: ignore userId for now.
+            try {
+                return mPackageManager.checkSignatures(packageName1, packageName2) == SIGNATURE_MATCH;
+            } catch (RemoteException e) {
+                // Intentionally left blank
+            }
+            return false;
+        }
+
+        @Override
+        public List<PackageInfo> getOverlayPackages(final int userId) {
+            return mPackageManagerInternal.getOverlayPackages(userId);
+        }
+
+        public PackageInfo getCachedPackageInfo(@NonNull final String packageName,
+                final int userId) {
+            final HashMap<String, PackageInfo> map = mCache.get(userId);
+            return map == null ? null : map.get(packageName);
+        }
+
+        public void cachePackageInfo(@NonNull final String packageName, final int userId,
+                @NonNull final PackageInfo pi) {
+            HashMap<String, PackageInfo> map = mCache.get(userId);
+            if (map == null) {
+                map = new HashMap<>();
+                mCache.put(userId, map);
+            }
+            map.put(packageName, pi);
+        }
+
+        public void forgetPackageInfo(@NonNull final String packageName, final int userId) {
+            final HashMap<String, PackageInfo> map = mCache.get(userId);
+            if (map == null) {
+                return;
+            }
+            map.remove(packageName);
+            if (map.isEmpty()) {
+                mCache.delete(userId);
+            }
+        }
+
+        public void forgetAllPackageInfos(final int userId) {
+            mCache.delete(userId);
+        }
+
+        private static final String TAB1 = "    ";
+        private static final String TAB2 = TAB1 + TAB1;
+
+        public void dump(@NonNull final PrintWriter pw, final boolean verbose) {
+            pw.println("PackageInfo cache");
+
+            if (!verbose) {
+                int count = 0;
+                final int N = mCache.size();
+                for (int i = 0; i < N; i++) {
+                    final int userId = mCache.keyAt(i);
+                    count += mCache.get(userId).size();
+                }
+                pw.println(TAB1 + count + " package(s)");
+                return;
+            }
+
+            if (mCache.size() == 0) {
+                pw.println(TAB1 + "<empty>");
+                return;
+            }
+
+            final int N = mCache.size();
+            for (int i = 0; i < N; i++) {
+                final int userId = mCache.keyAt(i);
+                pw.println(TAB1 + "User " + userId);
+                final HashMap<String, PackageInfo> map = mCache.get(userId);
+                for (Map.Entry<String, PackageInfo> entry : map.entrySet()) {
+                    pw.println(TAB2 + entry.getKey() + ": " + entry.getValue());
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
new file mode 100644
index 0000000..0e33409
--- /dev/null
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -0,0 +1,405 @@
+/*
+ * 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.server.om;
+
+import static android.content.om.OverlayInfo.STATE_DISABLED;
+import static android.content.om.OverlayInfo.STATE_ENABLED;
+import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
+import static android.content.om.OverlayInfo.STATE_NO_IDMAP;
+
+import static com.android.server.om.OverlayManagerService.DEBUG;
+import static com.android.server.om.OverlayManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.om.OverlayInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Internal implementation of OverlayManagerService.
+ *
+ * Methods in this class should only be called by the OverlayManagerService.
+ * This class is not thread-safe; the caller is expected to ensure the
+ * necessary thread synchronization.
+ *
+ * @see OverlayManagerService
+ */
+final class OverlayManagerServiceImpl {
+    private final PackageManagerHelper mPackageManager;
+    private final IdmapManager mIdmapManager;
+    private final OverlayManagerSettings mSettings;
+
+    OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager,
+            @NonNull final IdmapManager idmapManager,
+            @NonNull final OverlayManagerSettings settings) {
+        mPackageManager = packageManager;
+        mIdmapManager = idmapManager;
+        mSettings = settings;
+    }
+
+    /*
+     * Call this when switching to a new Android user. Will return a list of
+     * target packages that must refresh their overlays. This list is the union
+     * of two sets: the set of targets with currently active overlays, and the
+     * set of targets that had, but no longer have, active overlays.
+     */
+    List<String> onSwitchUser(final int newUserId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onSwitchUser newUserId=" + newUserId);
+        }
+
+        final Set<String> packagesToUpdateAssets = new ArraySet<>();
+        final ArrayMap<String, List<OverlayInfo>> tmp = mSettings.getOverlaysForUser(newUserId);
+        final int tmpSize = tmp.size();
+        final ArrayMap<String, OverlayInfo> storedOverlayInfos = new ArrayMap<>(tmpSize);
+        for (int i = 0; i < tmpSize; i++) {
+            final List<OverlayInfo> chunk = tmp.valueAt(i);
+            final int chunkSize = chunk.size();
+            for (int j = 0; j < chunkSize; j++) {
+                final OverlayInfo oi = chunk.get(j);
+                storedOverlayInfos.put(oi.packageName, oi);
+            }
+        }
+
+        List<PackageInfo> overlayPackages = mPackageManager.getOverlayPackages(newUserId);
+        final int overlayPackagesSize = overlayPackages.size();
+        for (int i = 0; i < overlayPackagesSize; i++) {
+            final PackageInfo overlayPackage = overlayPackages.get(i);
+            final OverlayInfo oi = storedOverlayInfos.get(overlayPackage.packageName);
+            if (oi == null || !oi.targetPackageName.equals(overlayPackage.overlayTarget)) {
+                if (oi != null) {
+                    packagesToUpdateAssets.add(oi.targetPackageName);
+                }
+                mSettings.init(overlayPackage.packageName, newUserId,
+                        overlayPackage.overlayTarget,
+                        overlayPackage.applicationInfo.getBaseCodePath());
+            }
+
+            try {
+                final PackageInfo targetPackage =
+                        mPackageManager.getPackageInfo(overlayPackage.overlayTarget, newUserId);
+                updateState(targetPackage, overlayPackage, newUserId);
+            } catch (OverlayManagerSettings.BadKeyException e) {
+                Slog.e(TAG, "failed to update settings", e);
+                mSettings.remove(overlayPackage.packageName, newUserId);
+            }
+
+            packagesToUpdateAssets.add(overlayPackage.overlayTarget);
+            storedOverlayInfos.remove(overlayPackage.packageName);
+        }
+
+        // any OverlayInfo left in storedOverlayInfos is no longer
+        // installed and should be removed
+        final int storedOverlayInfosSize = storedOverlayInfos.size();
+        for (int i = 0; i < storedOverlayInfosSize; i++) {
+            final OverlayInfo oi = storedOverlayInfos.get(i);
+            mSettings.remove(oi.packageName, oi.userId);
+            removeIdmapIfPossible(oi);
+            packagesToUpdateAssets.add(oi.targetPackageName);
+        }
+
+        // remove target packages that are not installed
+        final Iterator<String> iter = packagesToUpdateAssets.iterator();
+        while (iter.hasNext()) {
+            String targetPackageName = iter.next();
+            if (mPackageManager.getPackageInfo(targetPackageName, newUserId) == null) {
+                iter.remove();
+            }
+        }
+
+        return new ArrayList<String>(packagesToUpdateAssets);
+    }
+
+    void onUserRemoved(final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onUserRemoved userId=" + userId);
+        }
+        mSettings.removeUser(userId);
+    }
+
+    void onTargetPackageAdded(@NonNull final String packageName, final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId);
+        }
+
+        final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
+        updateAllOverlaysForTarget(packageName, userId, targetPackage);
+    }
+
+    void onTargetPackageChanged(@NonNull final String packageName, final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId);
+        }
+
+        final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
+        updateAllOverlaysForTarget(packageName, userId, targetPackage);
+    }
+
+    void onTargetPackageUpgrading(@NonNull final String packageName, final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId=" + userId);
+        }
+
+        updateAllOverlaysForTarget(packageName, userId, null);
+    }
+
+    void onTargetPackageUpgraded(@NonNull final String packageName, final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onTargetPackageUpgraded packageName=" + packageName + " userId=" + userId);
+        }
+
+        final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
+        updateAllOverlaysForTarget(packageName, userId, targetPackage);
+    }
+
+    void onTargetPackageRemoved(@NonNull final String packageName, final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId);
+        }
+
+        updateAllOverlaysForTarget(packageName, userId, null);
+    }
+
+    private void updateAllOverlaysForTarget(@NonNull final String packageName, final int userId,
+            @Nullable final PackageInfo targetPackage) {
+        final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(packageName, userId);
+        final int N = ois.size();
+        for (int i = 0; i < N; i++) {
+            final OverlayInfo oi = ois.get(i);
+            final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName, userId);
+            if (overlayPackage == null) {
+                mSettings.remove(oi.packageName, oi.userId);
+                removeIdmapIfPossible(oi);
+            } else {
+                try {
+                    updateState(targetPackage, overlayPackage, userId);
+                } catch (OverlayManagerSettings.BadKeyException e) {
+                    Slog.e(TAG, "failed to update settings", e);
+                    mSettings.remove(oi.packageName, userId);
+                }
+            }
+        }
+    }
+
+    void onOverlayPackageAdded(@NonNull final String packageName, final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onOverlayPackageAdded packageName=" + packageName + " userId=" + userId);
+        }
+
+        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
+        if (overlayPackage == null) {
+            Slog.w(TAG, "overlay package " + packageName + " was added, but couldn't be found");
+            onOverlayPackageRemoved(packageName, userId);
+            return;
+        }
+
+        final PackageInfo targetPackage =
+                mPackageManager.getPackageInfo(overlayPackage.overlayTarget, userId);
+
+        mSettings.init(packageName, userId, overlayPackage.overlayTarget,
+                overlayPackage.applicationInfo.getBaseCodePath());
+        try {
+            updateState(targetPackage, overlayPackage, userId);
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            Slog.e(TAG, "failed to update settings", e);
+            mSettings.remove(packageName, userId);
+        }
+    }
+
+    void onOverlayPackageChanged(@NonNull final String packageName, final int userId) {
+        Slog.wtf(TAG, "onOverlayPackageChanged called, but only pre-installed overlays supported");
+    }
+
+    void onOverlayPackageUpgrading(@NonNull final String packageName, final int userId) {
+        Slog.wtf(TAG, "onOverlayPackageUpgrading called, but only pre-installed overlays supported");
+    }
+
+    void onOverlayPackageUpgraded(@NonNull final String packageName, final int userId) {
+        Slog.wtf(TAG, "onOverlayPackageUpgraded called, but only pre-installed overlays supported");
+    }
+
+    void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) {
+        Slog.wtf(TAG, "onOverlayPackageRemoved called, but only pre-installed overlays supported");
+    }
+
+    OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) {
+        try {
+            return mSettings.getOverlayInfo(packageName, userId);
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            return null;
+        }
+    }
+
+    List<OverlayInfo> getOverlayInfosForTarget(@NonNull final String targetPackageName,
+            final int userId) {
+        return mSettings.getOverlaysForTarget(targetPackageName, userId);
+    }
+
+    Map<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
+        return mSettings.getOverlaysForUser(userId);
+    }
+
+    boolean setEnabled(@NonNull final String packageName, final boolean enable,
+            final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, String.format("setEnabled packageName=%s enable=%s userId=%d",
+                        packageName, enable, userId));
+        }
+
+        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
+        if (overlayPackage == null) {
+            return false;
+        }
+
+        try {
+            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+            final PackageInfo targetPackage =
+                    mPackageManager.getPackageInfo(oi.targetPackageName, userId);
+            mSettings.setEnabled(packageName, userId, enable);
+            updateState(targetPackage, overlayPackage, userId);
+            return true;
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            return false;
+        }
+    }
+
+    boolean setPriority(@NonNull final String packageName,
+            @NonNull final String newParentPackageName, final int userId) {
+        return mSettings.setPriority(packageName, newParentPackageName, userId);
+    }
+
+    boolean setHighestPriority(@NonNull final String packageName, final int userId) {
+        return mSettings.setHighestPriority(packageName, userId);
+    }
+
+    boolean setLowestPriority(@NonNull final String packageName, final int userId) {
+        return mSettings.setLowestPriority(packageName, userId);
+    }
+
+    void onDump(@NonNull final PrintWriter pw) {
+        mSettings.dump(pw);
+    }
+
+    List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName,
+            final int userId) {
+        final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, userId);
+        final List<String> paths = new ArrayList<>(overlays.size());
+        final int N = overlays.size();
+        for (int i = 0; i < N; i++) {
+            final OverlayInfo oi = overlays.get(i);
+            if (oi.isEnabled()) {
+                paths.add(oi.packageName);
+            }
+        }
+        return paths;
+    }
+
+    private void updateState(@Nullable final PackageInfo targetPackage,
+            @NonNull final PackageInfo overlayPackage, final int userId)
+        throws OverlayManagerSettings.BadKeyException {
+        if (targetPackage != null) {
+            mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
+        }
+
+        mSettings.setBaseCodePath(overlayPackage.packageName, userId,
+                overlayPackage.applicationInfo.getBaseCodePath());
+
+        final int currentState = mSettings.getState(overlayPackage.packageName, userId);
+        final int newState = calculateNewState(targetPackage, overlayPackage, userId);
+        if (currentState != newState) {
+            if (DEBUG) {
+                Slog.d(TAG, String.format("%s:%d: %s -> %s",
+                            overlayPackage.packageName, userId,
+                            OverlayInfo.stateToString(currentState),
+                            OverlayInfo.stateToString(newState)));
+            }
+            mSettings.setState(overlayPackage.packageName, userId, newState);
+        }
+    }
+
+    private int calculateNewState(@Nullable final PackageInfo targetPackage,
+            @NonNull final PackageInfo overlayPackage, final int userId)
+        throws OverlayManagerSettings.BadKeyException {
+        if (targetPackage == null) {
+            return STATE_MISSING_TARGET;
+        }
+
+        if (!mIdmapManager.idmapExists(overlayPackage, userId)) {
+            return STATE_NO_IDMAP;
+        }
+
+        final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId);
+        return enabled ? STATE_ENABLED : STATE_DISABLED;
+    }
+
+    private void removeIdmapIfPossible(@NonNull final OverlayInfo oi) {
+        // For a given package, all Android users share the same idmap file.
+        // This works because Android currently does not support users to
+        // install different versions of the same package. It also means we
+        // cannot remove an idmap file if any user still needs it.
+        //
+        // When/if the Android framework allows different versions of the same
+        // package to be installed for different users, idmap file handling
+        // should be revised:
+        //
+        // - an idmap file should be unique for each {user, package} pair
+        //
+        // - the path to the idmap file should be passed to the native Asset
+        //   Manager layers, just like the path to the apk is passed today
+        //
+        // As part of that change, calls to this method should be replaced by
+        // direct calls to IdmapManager.removeIdmap, without looping over all
+        // users.
+
+        if (!mIdmapManager.idmapExists(oi)) {
+            return;
+        }
+        final List<Integer> userIds = mSettings.getUsers();
+        final int N = userIds.size();
+        for (int i = 0; i < N; i++) {
+            final int userId = userIds.get(i);
+            try {
+                final OverlayInfo tmp = mSettings.getOverlayInfo(oi.packageName, userId);
+                if (tmp != null && tmp.isEnabled()) {
+                    // someone is still using the idmap file -> we cannot remove it
+                    return;
+                }
+            } catch (OverlayManagerSettings.BadKeyException e) {
+                // intentionally left empty
+            }
+        }
+        mIdmapManager.removeIdmap(oi, oi.userId);
+    }
+
+    interface PackageManagerHelper {
+        PackageInfo getPackageInfo(@NonNull String packageName, int userId);
+        boolean signaturesMatching(@NonNull String packageName1, @NonNull String packageName2,
+                                   int userId);
+        List<PackageInfo> getOverlayPackages(int userId);
+    }
+}
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
new file mode 100644
index 0000000..44908a7
--- /dev/null
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -0,0 +1,630 @@
+/*
+ * 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.server.om;
+
+import static android.content.om.OverlayInfo.STATE_UNKNOWN;
+
+import static com.android.server.om.OverlayManagerService.DEBUG;
+import static com.android.server.om.OverlayManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.om.OverlayInfo;
+import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+/**
+ * Data structure representing the current state of all overlay packages in the
+ * system.
+ *
+ * Modifications to the data are exposed through the ChangeListener interface.
+ *
+ * @see ChangeListener
+ * @see OverlayManagerService
+ */
+final class OverlayManagerSettings {
+    private final List<ChangeListener> mListeners = new ArrayList<>();
+
+    private final ArrayList<SettingsItem> mItems = new ArrayList<>();
+
+    void init(@NonNull final String packageName, final int userId,
+            @NonNull final String targetPackageName, @NonNull final String baseCodePath) {
+        remove(packageName, userId);
+        final SettingsItem item =
+                new SettingsItem(packageName, userId, targetPackageName, baseCodePath);
+        mItems.add(item);
+    }
+
+    void remove(@NonNull final String packageName, final int userId) {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            return;
+        }
+        final OverlayInfo oi = item.getOverlayInfo();
+        mItems.remove(item);
+        if (oi != null) {
+            notifyOverlayRemoved(oi);
+        }
+    }
+
+    boolean contains(@NonNull final String packageName, final int userId) {
+        return select(packageName, userId) != null;
+    }
+
+    OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
+            throws BadKeyException {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            throw new BadKeyException(packageName, userId);
+        }
+        return item.getOverlayInfo();
+    }
+
+    String getTargetPackageName(@NonNull final String packageName, final int userId)
+            throws BadKeyException {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            throw new BadKeyException(packageName, userId);
+        }
+        return item.getTargetPackageName();
+    }
+
+    void setBaseCodePath(@NonNull final String packageName, final int userId,
+            @NonNull final String path) throws BadKeyException {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            throw new BadKeyException(packageName, userId);
+        }
+        item.setBaseCodePath(path);
+        notifySettingsChanged();
+    }
+
+    boolean getEnabled(@NonNull final String packageName, final int userId) throws BadKeyException {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            throw new BadKeyException(packageName, userId);
+        }
+        return item.isEnabled();
+    }
+
+    void setEnabled(@NonNull final String packageName, final int userId, final boolean enable)
+            throws BadKeyException {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            throw new BadKeyException(packageName, userId);
+        }
+        if (enable == item.isEnabled()) {
+            return; // nothing to do
+        }
+
+        item.setEnabled(enable);
+        notifySettingsChanged();
+    }
+
+    int getState(@NonNull final String packageName, final int userId) throws BadKeyException {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            throw new BadKeyException(packageName, userId);
+        }
+        return item.getState();
+    }
+
+    void setState(@NonNull final String packageName, final int userId, final int state)
+            throws BadKeyException {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            throw new BadKeyException(packageName, userId);
+        }
+        final OverlayInfo previous = item.getOverlayInfo();
+        item.setState(state);
+        final OverlayInfo current = item.getOverlayInfo();
+        if (previous.state == STATE_UNKNOWN) {
+            notifyOverlayAdded(current);
+            notifySettingsChanged();
+        } else if (current.state != previous.state) {
+            notifyOverlayChanged(current, previous);
+            notifySettingsChanged();
+        }
+    }
+
+    List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName,
+            final int userId) {
+        final List<SettingsItem> items = selectWhereTarget(targetPackageName, userId);
+        if (items.isEmpty()) {
+            return Collections.emptyList();
+        }
+        final List<OverlayInfo> out = new ArrayList<>(items.size());
+        final int N = items.size();
+        for (int i = 0; i < N; i++) {
+            final SettingsItem item = items.get(i);
+            out.add(item.getOverlayInfo());
+        }
+        return out;
+    }
+
+    ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
+        final List<SettingsItem> items = selectWhereUser(userId);
+        if (items.isEmpty()) {
+            return ArrayMap.EMPTY;
+        }
+        final ArrayMap<String, List<OverlayInfo>> out = new ArrayMap<>(items.size());
+        final int N = items.size();
+        for (int i = 0; i < N; i++) {
+            final SettingsItem item = items.get(i);
+            final String targetPackageName = item.getTargetPackageName();
+            if (!out.containsKey(targetPackageName)) {
+                out.put(targetPackageName, new ArrayList<OverlayInfo>());
+            }
+            final List<OverlayInfo> overlays = out.get(targetPackageName);
+            overlays.add(item.getOverlayInfo());
+        }
+        return out;
+    }
+
+    List<String> getTargetPackageNamesForUser(final int userId) {
+        final List<SettingsItem> items = selectWhereUser(userId);
+        if (items.isEmpty()) {
+            return Collections.emptyList();
+        }
+        final List<String> out = new ArrayList<>();
+        final int N = items.size();
+        for (int i = 0; i < N; i++) {
+            final SettingsItem item = items.get(i);
+            final String targetPackageName = item.getTargetPackageName();
+            if (!out.contains(targetPackageName)) {
+                out.add(targetPackageName);
+            }
+        }
+        return out;
+    }
+
+    List<Integer> getUsers() {
+        final ArrayList<Integer> users = new ArrayList<>();
+        final int N = mItems.size();
+        for (int i = 0; i < N; i++) {
+            final SettingsItem item = mItems.get(i);
+            if (!users.contains(item.userId)) {
+                users.add(item.userId);
+            }
+        }
+        return users;
+    }
+
+    void removeUser(final int userId) {
+        final Iterator<SettingsItem> iter = mItems.iterator();
+        while (iter.hasNext()) {
+            final SettingsItem item = iter.next();
+            if (item.userId == userId) {
+                iter.remove();
+            }
+        }
+    }
+
+    boolean setPriority(@NonNull final String packageName,
+            @NonNull final String newParentPackageName, final int userId) {
+        if (packageName.equals(newParentPackageName)) {
+            return false;
+        }
+        final SettingsItem rowToMove = select(packageName, userId);
+        if (rowToMove == null) {
+            return false;
+        }
+        final SettingsItem newParentRow = select(newParentPackageName, userId);
+        if (newParentRow == null) {
+            return false;
+        }
+        if (!rowToMove.getTargetPackageName().equals(newParentRow.getTargetPackageName())) {
+            return false;
+        }
+
+        mItems.remove(rowToMove);
+        final ListIterator<SettingsItem> iter = mItems.listIterator();
+        while (iter.hasNext()) {
+            final SettingsItem item = iter.next();
+            if (item.userId == userId && item.packageName.equals(newParentPackageName)) {
+                iter.add(rowToMove);
+                notifyOverlayPriorityChanged(rowToMove.getOverlayInfo());
+                notifySettingsChanged();
+                return true;
+            }
+        }
+
+        Slog.wtf(TAG, "failed to find the parent item a second time");
+        return false;
+    }
+
+    boolean setLowestPriority(@NonNull final String packageName, final int userId) {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            return false;
+        }
+        mItems.remove(item);
+        mItems.add(0, item);
+        notifyOverlayPriorityChanged(item.getOverlayInfo());
+        notifySettingsChanged();
+        return true;
+    }
+
+    boolean setHighestPriority(@NonNull final String packageName, final int userId) {
+        final SettingsItem item = select(packageName, userId);
+        if (item == null) {
+            return false;
+        }
+        mItems.remove(item);
+        mItems.add(item);
+        notifyOverlayPriorityChanged(item.getOverlayInfo());
+        notifySettingsChanged();
+        return true;
+    }
+
+    private static final String TAB1 = "    ";
+    private static final String TAB2 = TAB1 + TAB1;
+    private static final String TAB3 = TAB2 + TAB1;
+
+    void dump(@NonNull final PrintWriter pw) {
+        pw.println("Settings");
+        dumpItems(pw);
+        dumpListeners(pw);
+    }
+
+    private void dumpItems(@NonNull final PrintWriter pw) {
+        pw.println(TAB1 + "Items");
+
+        if (mItems.isEmpty()) {
+            pw.println(TAB2 + "<none>");
+            return;
+        }
+
+        final int N = mItems.size();
+        for (int i = 0; i < N; i++) {
+            final SettingsItem item = mItems.get(i);
+            final StringBuilder sb = new StringBuilder();
+            sb.append(TAB2 + item.packageName + ":" + item.userId + " {\n");
+            sb.append(TAB3 + "packageName.......: " + item.packageName + "\n");
+            sb.append(TAB3 + "userId............: " + item.userId + "\n");
+            sb.append(TAB3 + "targetPackageName.: " + item.getTargetPackageName() + "\n");
+            sb.append(TAB3 + "baseCodePath......: " + item.getBaseCodePath() + "\n");
+            sb.append(TAB3 + "state.............: " + OverlayInfo.stateToString(item.getState()) + "\n");
+            sb.append(TAB3 + "isEnabled.........: " + item.isEnabled() + "\n");
+            sb.append(TAB2 + "}");
+            pw.println(sb.toString());
+        }
+    }
+
+    private void dumpListeners(@NonNull final PrintWriter pw) {
+        pw.println(TAB1 + "Change listeners");
+
+        if (mListeners.isEmpty()) {
+            pw.println(TAB2 + "<none>");
+            return;
+        }
+
+        final int N = mListeners.size();
+        for (int i = 0; i < N; i++) {
+            final ChangeListener ch = mListeners.get(i);
+            pw.println(TAB2 + ch);
+        }
+
+    }
+
+    void restore(@NonNull final InputStream is) throws IOException, XmlPullParserException {
+        Serializer.restore(mItems, is);
+    }
+
+    void persist(@NonNull final OutputStream os) throws IOException, XmlPullParserException {
+        Serializer.persist(mItems, os);
+    }
+
+    private static final class Serializer {
+        private static final String TAG_OVERLAYS = "overlays";
+        private static final String TAG_ITEM = "item";
+
+        private static final String ATTR_BASE_CODE_PATH = "baseCodePath";
+        private static final String ATTR_IS_ENABLED = "isEnabled";
+        private static final String ATTR_PACKAGE_NAME = "packageName";
+        private static final String ATTR_STATE = "state";
+        private static final String ATTR_TARGET_PACKAGE_NAME = "targetPackageName";
+        private static final String ATTR_USER_ID = "userId";
+        private static final String ATTR_VERSION = "version";
+
+        private static final int CURRENT_VERSION = 1;
+
+        public static void restore(@NonNull final ArrayList<SettingsItem> table,
+                @NonNull final InputStream is) throws IOException, XmlPullParserException {
+
+            try (InputStreamReader reader = new InputStreamReader(is)) {
+                table.clear();
+                final XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(reader);
+                XmlUtils.beginDocument(parser, TAG_OVERLAYS);
+                int version = XmlUtils.readIntAttribute(parser, ATTR_VERSION);
+                if (version != CURRENT_VERSION) {
+                    throw new XmlPullParserException("unrecognized version " + version);
+                }
+                int depth = parser.getDepth();
+
+                while (XmlUtils.nextElementWithin(parser, depth)) {
+                    switch (parser.getName()) {
+                        case TAG_ITEM:
+                            final SettingsItem item = restoreRow(parser, depth + 1);
+                            table.add(item);
+                            break;
+                    }
+                }
+            }
+        }
+
+        private static SettingsItem restoreRow(@NonNull final XmlPullParser parser, final int depth)
+                throws IOException {
+            final String packageName = XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME);
+            final int userId = XmlUtils.readIntAttribute(parser, ATTR_USER_ID);
+            final String targetPackageName = XmlUtils.readStringAttribute(parser,
+                    ATTR_TARGET_PACKAGE_NAME);
+            final String baseCodePath = XmlUtils.readStringAttribute(parser, ATTR_BASE_CODE_PATH);
+            final int state = XmlUtils.readIntAttribute(parser, ATTR_STATE);
+            final boolean isEnabled = XmlUtils.readBooleanAttribute(parser, ATTR_IS_ENABLED);
+
+            return new SettingsItem(packageName, userId, targetPackageName, baseCodePath, state,
+                    isEnabled);
+        }
+
+        public static void persist(@NonNull final ArrayList<SettingsItem> table,
+                @NonNull final OutputStream os) throws IOException, XmlPullParserException {
+            final FastXmlSerializer xml = new FastXmlSerializer();
+            xml.setOutput(os, "utf-8");
+            xml.startDocument(null, true);
+            xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            xml.startTag(null, TAG_OVERLAYS);
+            XmlUtils.writeIntAttribute(xml, ATTR_VERSION, CURRENT_VERSION);
+
+            final int N = table.size();
+            for (int i = 0; i < N; i++) {
+                final SettingsItem item = table.get(i);
+                persistRow(xml, item);
+            }
+            xml.endTag(null, TAG_OVERLAYS);
+            xml.endDocument();
+        }
+
+        private static void persistRow(@NonNull final FastXmlSerializer xml,
+                @NonNull final SettingsItem item) throws IOException {
+            xml.startTag(null, TAG_ITEM);
+            XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.packageName);
+            XmlUtils.writeIntAttribute(xml, ATTR_USER_ID, item.userId);
+            XmlUtils.writeStringAttribute(xml, ATTR_TARGET_PACKAGE_NAME, item.targetPackageName);
+            XmlUtils.writeStringAttribute(xml, ATTR_BASE_CODE_PATH, item.baseCodePath);
+            XmlUtils.writeIntAttribute(xml, ATTR_STATE, item.state);
+            XmlUtils.writeBooleanAttribute(xml, ATTR_IS_ENABLED, item.isEnabled);
+            xml.endTag(null, TAG_ITEM);
+        }
+    }
+
+    private static final class SettingsItem {
+        private final int userId;
+        private final String packageName;
+        private final String targetPackageName;
+        private String baseCodePath;
+        private int state;
+        private boolean isEnabled;
+        private OverlayInfo cache;
+
+        SettingsItem(@NonNull final String packageName, final int userId,
+                @NonNull final String targetPackageName, @NonNull final String baseCodePath,
+                final int state, final boolean isEnabled) {
+            this.packageName = packageName;
+            this.userId = userId;
+            this.targetPackageName = targetPackageName;
+            this.baseCodePath = baseCodePath;
+            this.state = state;
+            this.isEnabled = isEnabled;
+            cache = null;
+        }
+
+        SettingsItem(@NonNull final String packageName, final int userId,
+                @NonNull final String targetPackageName, @NonNull final String baseCodePath) {
+            this(packageName, userId, targetPackageName, baseCodePath, STATE_UNKNOWN,
+                    false);
+        }
+
+        private String getTargetPackageName() {
+            return targetPackageName;
+        }
+
+        private String getBaseCodePath() {
+            return baseCodePath;
+        }
+
+        private void setBaseCodePath(@NonNull final String path) {
+            if (!baseCodePath.equals(path)) {
+                baseCodePath = path;
+                invalidateCache();
+            }
+        }
+
+        private int getState() {
+            return state;
+        }
+
+        private void setState(final int state) {
+            if (this.state != state) {
+                this.state = state;
+                invalidateCache();
+            }
+        }
+
+        private boolean isEnabled() {
+            return isEnabled;
+        }
+
+        private void setEnabled(final boolean enable) {
+            if (isEnabled != enable) {
+                isEnabled = enable;
+                invalidateCache();
+            }
+        }
+
+        private OverlayInfo getOverlayInfo() {
+            if (cache == null) {
+                cache = new OverlayInfo(packageName, targetPackageName, baseCodePath,
+                        state, userId);
+            }
+            return cache;
+        }
+
+        private void invalidateCache() {
+            cache = null;
+        }
+    }
+
+    private SettingsItem select(@NonNull final String packageName, final int userId) {
+        final int N = mItems.size();
+        for (int i = 0; i < N; i++) {
+            final SettingsItem item = mItems.get(i);
+            if (item.userId == userId && item.packageName.equals(packageName)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    private List<SettingsItem> selectWhereUser(final int userId) {
+        final ArrayList<SettingsItem> items = new ArrayList<>();
+        final int N = mItems.size();
+        for (int i = 0; i < N; i++) {
+            final SettingsItem item = mItems.get(i);
+            if (item.userId == userId) {
+                items.add(item);
+            }
+        }
+        return items;
+    }
+
+    private List<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
+            final int userId) {
+        final ArrayList<SettingsItem> items = new ArrayList<>();
+        final int N = mItems.size();
+        for (int i = 0; i < N; i++) {
+            final SettingsItem item = mItems.get(i);
+            if (item.userId == userId && item.getTargetPackageName().equals(targetPackageName)) {
+                items.add(item);
+            }
+        }
+        return items;
+    }
+
+    private void assertNotNull(@Nullable final Object o) {
+        if (o == null) {
+            throw new AndroidRuntimeException("object must not be null");
+        }
+    }
+
+    void addChangeListener(@NonNull final ChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    void removeChangeListener(@NonNull final ChangeListener listener) {
+        mListeners.remove(listener);
+    }
+
+    private void notifySettingsChanged() {
+        final int N = mListeners.size();
+        for (int i = 0; i < N; i++) {
+            final ChangeListener listener = mListeners.get(i);
+            listener.onSettingsChanged();
+        }
+    }
+
+    private void notifyOverlayAdded(@NonNull final OverlayInfo oi) {
+        if (DEBUG) {
+            assertNotNull(oi);
+        }
+        final int N = mListeners.size();
+        for (int i = 0; i < N; i++) {
+            final ChangeListener listener = mListeners.get(i);
+            listener.onOverlayAdded(oi);
+        }
+    }
+
+    private void notifyOverlayRemoved(@NonNull final OverlayInfo oi) {
+        if (DEBUG) {
+            assertNotNull(oi);
+        }
+        final int N = mListeners.size();
+        for (int i = 0; i < N; i++) {
+            final ChangeListener listener = mListeners.get(i);
+            listener.onOverlayRemoved(oi);
+        }
+    }
+
+    private void notifyOverlayChanged(@NonNull final OverlayInfo oi,
+            @NonNull final OverlayInfo oldOi) {
+        if (DEBUG) {
+            assertNotNull(oi);
+            assertNotNull(oldOi);
+        }
+        final int N = mListeners.size();
+        for (int i = 0; i < N; i++) {
+            final ChangeListener listener = mListeners.get(i);
+            listener.onOverlayChanged(oi, oldOi);
+        }
+    }
+
+    private void notifyOverlayPriorityChanged(@NonNull final OverlayInfo oi) {
+        if (DEBUG) {
+            assertNotNull(oi);
+        }
+        final int N = mListeners.size();
+        for (int i = 0; i < N; i++) {
+            final ChangeListener listener = mListeners.get(i);
+            listener.onOverlayPriorityChanged(oi);
+        }
+    }
+
+    interface ChangeListener {
+        void onSettingsChanged();
+        void onOverlayAdded(@NonNull OverlayInfo oi);
+        void onOverlayRemoved(@NonNull OverlayInfo oi);
+        void onOverlayChanged(@NonNull OverlayInfo oi, @NonNull OverlayInfo oldOi);
+        void onOverlayPriorityChanged(@NonNull OverlayInfo oi);
+    }
+
+    static final class BadKeyException extends RuntimeException {
+        BadKeyException(@NonNull final String packageName, final int userId) {
+            super("Bad key packageName=" + packageName + " userId=" + userId);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
new file mode 100644
index 0000000..29ddaf4
--- /dev/null
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -0,0 +1,187 @@
+/*
+ * 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.server.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.om.IOverlayManager;
+import android.content.om.OverlayInfo;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of 'cmd overlay' commands.
+ *
+ * This class provides an interface to the OverlayManagerService via adb.
+ * Intended only for manual debugging. Execute 'adb exec-out cmd overlay help'
+ * for a list of available commands.
+ */
+final class OverlayManagerShellCommand extends ShellCommand {
+    private final IOverlayManager mInterface;
+
+    OverlayManagerShellCommand(@NonNull final IOverlayManager iom) {
+        mInterface = iom;
+    }
+
+    @Override
+    public int onCommand(@Nullable final String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter err = getErrPrintWriter();
+        try {
+            switch (cmd) {
+                case "list":
+                    return runList();
+                case "enable":
+                    return runEnableDisable(true);
+                case "disable":
+                    return runEnableDisable(false);
+                case "set-priority":
+                    return runSetPriority();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (IllegalArgumentException e) {
+            err.println("Error: " + e.getMessage());
+        } catch (RemoteException e) {
+            err.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter out = getOutPrintWriter();
+        out.println("Overlay manager (overlay) commands:");
+        out.println("  help");
+        out.println("    Print this help text.");
+        out.println("  dump [--verbose] [--user USER_ID] [PACKAGE [PACKAGE [...]]]");
+        out.println("    Print debugging information about the overlay manager.");
+        out.println("  list [--user USER_ID] [PACKAGE [PACKAGE [...]]]");
+        out.println("    Print information about target and overlay packages.");
+        out.println("    Overlay packages are printed in priority order. With optional");
+        out.println("    parameters PACKAGEs, limit output to the specified packages");
+        out.println("    but include more information about each package.");
+        out.println("  enable [--user USER_ID] PACKAGE");
+        out.println("    Enable overlay package PACKAGE.");
+        out.println("  disable [--user USER_ID] PACKAGE");
+        out.println("    Disable overlay package PACKAGE.");
+        out.println("  set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
+        out.println("    Change the priority of the overlay PACKAGE to be just higher than");
+        out.println("    the priority of PACKAGE_PARENT If PARENT is the special keyword");
+        out.println("    'lowest', change priority of PACKAGE to the lowest priority.");
+        out.println("    If PARENT is the special keyword 'highest', change priority of");
+        out.println("    PACKAGE to the highest priority.");
+    }
+
+    private int runList() throws RemoteException {
+        final PrintWriter out = getOutPrintWriter();
+        final PrintWriter err = getErrPrintWriter();
+
+        int userId = UserHandle.USER_SYSTEM;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    err.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final Map<String, List<OverlayInfo>> allOverlays = mInterface.getAllOverlays(userId);
+        for (final String targetPackageName : allOverlays.keySet()) {
+            out.println(targetPackageName);
+            List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName);
+            final int N = overlaysForTarget.size();
+            for (int i = 0; i < N; i++) {
+                final OverlayInfo oi = overlaysForTarget.get(i);
+                String status;
+                switch (oi.state) {
+                    case OverlayInfo.STATE_ENABLED:
+                        status = "[x]";
+                        break;
+                    case OverlayInfo.STATE_DISABLED:
+                        status = "[ ]";
+                        break;
+                    default:
+                        status = "---";
+                        break;
+                }
+                out.println(String.format("%s %s", status, oi.packageName));
+            }
+            out.println();
+        }
+        return 0;
+    }
+
+    private int runEnableDisable(final boolean enable) throws RemoteException {
+        final PrintWriter err = getErrPrintWriter();
+
+        int userId = UserHandle.USER_SYSTEM;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    err.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArgRequired();
+        return mInterface.setEnabled(packageName, enable, userId) ? 0 : 1;
+    }
+
+    private int runSetPriority() throws RemoteException {
+        final PrintWriter err = getErrPrintWriter();
+
+        int userId = UserHandle.USER_SYSTEM;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    err.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArgRequired();
+        final String newParentPackageName = getNextArgRequired();
+
+        if ("highest".equals(newParentPackageName)) {
+            return mInterface.setHighestPriority(packageName, userId) ? 0 : 1;
+        } else if ("lowest".equals(newParentPackageName)) {
+            return mInterface.setLowestPriority(packageName, userId) ? 0 : 1;
+        } else {
+            return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 0de0c92..49d3c8b 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -287,7 +287,7 @@
         for (int i = 0; i < defMapSize; i++) {
             String alias = definedMapping.keyAt(i);
             ArraySet<PublicKey> pubKeys = definedMapping.valueAt(i);
-            if (alias != null && pubKeys != null || pubKeys.size() > 0) {
+            if (alias != null && pubKeys != null && pubKeys.size() > 0) {
                 KeySetHandle ks = addKeySetLPw(pubKeys);
                 newKeySetAliases.put(alias, ks.getId());
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 23a6a90..b0512d4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -831,6 +831,7 @@
     private List<String> mKeepUninstalledPackages;
 
     private UserManagerInternal mUserManagerInternal;
+    private final UserDataPreparer mUserDataPreparer;
 
     private File mCacheDir;
 
@@ -2262,8 +2263,8 @@
             mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
             mAsecInternalPath = new File(dataDir, "app-asec").getPath();
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
-
-            sUserManager = new UserManagerService(context, this, mPackages);
+            mUserDataPreparer = new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore);
+            sUserManager = new UserManagerService(context, this, mUserDataPreparer, mPackages);
 
             // Propagate permission configuration in to package manager.
             ArrayMap<String, SystemConfig.PermissionEntry> permConfig
@@ -21256,99 +21257,6 @@
     }
 
     /**
-     * Prepare storage areas for given user on all mounted devices.
-     */
-    void prepareUserData(int userId, int userSerial, int flags) {
-        synchronized (mInstallLock) {
-            final StorageManager storage = mContext.getSystemService(StorageManager.class);
-            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
-                final String volumeUuid = vol.getFsUuid();
-                prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
-            }
-        }
-    }
-
-    private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags,
-            boolean allowRecover) {
-        // Prepare storage and verify that serial numbers are consistent; if
-        // there's a mismatch we need to destroy to avoid leaking data
-        final StorageManager storage = mContext.getSystemService(StorageManager.class);
-        try {
-            storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
-
-            if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
-                UserManagerService.enforceSerialNumber(
-                        Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial);
-                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
-                    UserManagerService.enforceSerialNumber(
-                            Environment.getDataSystemDeDirectory(userId), userSerial);
-                }
-            }
-            if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
-                UserManagerService.enforceSerialNumber(
-                        Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial);
-                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
-                    UserManagerService.enforceSerialNumber(
-                            Environment.getDataSystemCeDirectory(userId), userSerial);
-                }
-            }
-
-            synchronized (mInstallLock) {
-                mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
-            }
-        } catch (Exception e) {
-            logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
-                    + " because we failed to prepare: " + e);
-            destroyUserDataLI(volumeUuid, userId,
-                    StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
-
-            if (allowRecover) {
-                // Try one last time; if we fail again we're really in trouble
-                prepareUserDataLI(volumeUuid, userId, userSerial, flags, false);
-            }
-        }
-    }
-
-    /**
-     * Destroy storage areas for given user on all mounted devices.
-     */
-    void destroyUserData(int userId, int flags) {
-        synchronized (mInstallLock) {
-            final StorageManager storage = mContext.getSystemService(StorageManager.class);
-            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
-                final String volumeUuid = vol.getFsUuid();
-                destroyUserDataLI(volumeUuid, userId, flags);
-            }
-        }
-    }
-
-    private void destroyUserDataLI(String volumeUuid, int userId, int flags) {
-        final StorageManager storage = mContext.getSystemService(StorageManager.class);
-        try {
-            // Clean up app data, profile data, and media data
-            mInstaller.destroyUserData(volumeUuid, userId, flags);
-
-            // Clean up system data
-            if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
-                if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
-                    FileUtils.deleteContentsAndDir(Environment.getUserSystemDirectory(userId));
-                    FileUtils.deleteContentsAndDir(Environment.getDataSystemDeDirectory(userId));
-                }
-                if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
-                    FileUtils.deleteContentsAndDir(Environment.getDataSystemCeDirectory(userId));
-                }
-            }
-
-            // Data with special labels is now gone, so finish the job
-            storage.destroyUserStorage(volumeUuid, userId, flags);
-
-        } catch (Exception e) {
-            logCriticalInfo(Log.WARN,
-                    "Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e);
-        }
-    }
-
-    /**
      * Examine all users present on given mounted volume, and destroy data
      * belonging to users that are no longer valid, or whose user ID has been
      * recycled.
@@ -21363,6 +21271,8 @@
                 .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
         Collections.addAll(files, FileUtils
                 .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
+        Collections.addAll(files, FileUtils
+                .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
         for (File file : files) {
             if (!file.isDirectory()) continue;
 
@@ -21393,7 +21303,7 @@
 
             if (destroyUser) {
                 synchronized (mInstallLock) {
-                    destroyUserDataLI(volumeUuid, userId,
+                    mUserDataPreparer.destroyUserDataLI(volumeUuid, userId,
                             StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
                 }
             }
@@ -22859,6 +22769,43 @@
                 mExternalSourcesPolicy = policy;
             }
         }
+
+        @Override
+        public List<PackageInfo> getOverlayPackages(int userId) {
+            final ArrayList<PackageInfo> overlayPackages = new ArrayList<PackageInfo>();
+            synchronized (mPackages) {
+                for (PackageParser.Package p : mPackages.values()) {
+                    if (p.mOverlayTarget != null) {
+                        PackageInfo pkg = generatePackageInfo((PackageSetting)p.mExtras, 0, userId);
+                        if (pkg != null) {
+                            overlayPackages.add(pkg);
+                        }
+                    }
+                }
+            }
+            return overlayPackages;
+        }
+
+        @Override
+        public List<String> getTargetPackageNames(int userId) {
+            List<String> targetPackages = new ArrayList<>();
+            synchronized (mPackages) {
+                for (PackageParser.Package p : mPackages.values()) {
+                    if (p.mOverlayTarget == null) {
+                        targetPackages.add(p.packageName);
+                    }
+                }
+            }
+            return targetPackages;
+        }
+
+
+        @Override
+        public boolean setEnabledOverlayPackages(int userId, String targetPackageName,
+                List<String> overlayPackageNames) {
+            // TODO: implement when we integrate OMS properly
+            return false;
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index 6e96726..6eac5e3 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -25,7 +25,6 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.PinItemRequest;
 import android.content.pm.ShortcutInfo;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.util.Log;
@@ -168,8 +167,8 @@
         mLock = lock;
     }
 
-    public boolean isRequestPinnedShortcutSupported(int callingUserId) {
-        return getRequestPinShortcutConfirmationActivity(callingUserId) != null;
+    public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
+        return getRequestPinConfirmationActivity(callingUserId, requestType) != null;
     }
 
     /**
@@ -185,8 +184,10 @@
         // First, make sure the launcher supports it.
 
         // Find the confirmation activity in the default launcher.
+        final int requestType = inShortcut != null ?
+                PinItemRequest.REQUEST_TYPE_SHORTCUT : PinItemRequest.REQUEST_TYPE_APPWIDGET;
         final Pair<ComponentName, Integer> confirmActivity =
-                getRequestPinShortcutConfirmationActivity(userId);
+                getRequestPinConfirmationActivity(userId, requestType);
 
         // If the launcher doesn't support it, just return a rejected result and finish.
         if (confirmActivity == null) {
@@ -210,7 +211,8 @@
             request = new PinItemRequest(inAppWidget,
                     new PinItemRequestInner(this, resultIntent, launcherUid));
         }
-        return startRequestConfirmActivity(confirmActivity.first, launcherUserId, request);
+        return startRequestConfirmActivity(confirmActivity.first, launcherUserId, request,
+                requestType);
     }
 
     /**
@@ -330,9 +332,13 @@
     }
 
     private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId,
-            PinItemRequest request) {
+            PinItemRequest request, int requestType) {
+        final String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ?
+                LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT :
+                LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET;
+
         // Start the activity.
-        final Intent confirmIntent = new Intent(LauncherApps.ACTION_CONFIRM_PIN_ITEM);
+        final Intent confirmIntent = new Intent(action);
         confirmIntent.setComponent(activity);
         confirmIntent.putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request);
         confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -351,13 +357,13 @@
     }
 
     /**
-     * Find the activity that handles {@link LauncherApps#ACTION_CONFIRM_PIN_ITEM} in the
+     * Find the activity that handles {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} in the
      * default launcher.
      */
     @Nullable
     @VisibleForTesting
-    Pair<ComponentName, Integer> getRequestPinShortcutConfirmationActivity(
-            int callingUserId) {
+    Pair<ComponentName, Integer> getRequestPinConfirmationActivity(
+            int callingUserId, int requestType) {
         // Find the default launcher.
         final int launcherUserId = mService.getParentOrSelfUserId(callingUserId);
         final ComponentName defaultLauncher = mService.getDefaultLauncher(launcherUserId);
@@ -367,7 +373,7 @@
             return null;
         }
         final ComponentName activity = mService.injectGetPinConfirmationActivity(
-                defaultLauncher.getPackageName(), launcherUserId);
+                defaultLauncher.getPackageName(), launcherUserId, requestType);
         return (activity == null) ? null : Pair.create(activity, launcherUserId);
     }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 56d679e..c0fc24c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2117,10 +2117,11 @@
     }
 
     @Override
-    public boolean isRequestPinShortcutSupported(int callingUserId) {
+    public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
         final long token = injectClearCallingIdentity();
         try {
-            return mShortcutRequestPinProcessor.isRequestPinnedShortcutSupported(callingUserId);
+            return mShortcutRequestPinProcessor
+                    .isRequestPinItemSupported(callingUserId, requestType);
         } finally {
             injectRestoreCallingIdentity(token);
         }
@@ -2621,6 +2622,11 @@
             Preconditions.checkNotNull(appWidget);
             return requestPinItem(callingPackage, userId, null, appWidget, resultIntent);
         }
+
+        @Override
+        public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
+            return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType);
+        }
     }
 
     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -3250,16 +3256,19 @@
     }
 
     /**
-     * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_ITEM} activity in a given package.
+     * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} or
+     * {@link LauncherApps#ACTION_CONFIRM_PIN_APPWIDGET} activity in a given package depending on
+     * the requestType.
      */
     @Nullable
     ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName,
-            int launcherUserId) {
+            int launcherUserId, int requestType) {
         Preconditions.checkNotNull(launcherPackageName);
+        String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ?
+                LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT :
+                LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET;
 
-        final Intent confirmIntent = new Intent(LauncherApps.ACTION_CONFIRM_PIN_ITEM);
-        confirmIntent.setPackage(launcherPackageName);
-
+        final Intent confirmIntent = new Intent(action).setPackage(launcherPackageName);
         final List<ResolveInfo> candidates = queryActivities(
                 confirmIntent, launcherUserId, /* exportedOnly =*/ false);
         for (ResolveInfo ri : candidates) {
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
new file mode 100644
index 0000000..52599fd
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -0,0 +1,139 @@
+/*
+ * 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.pm;
+
+import android.content.Context;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.util.Log;
+
+import java.util.Objects;
+
+import static com.android.server.pm.PackageManagerService.logCriticalInfo;
+
+/**
+ * Helper class for preparing and destroying user storage
+ */
+class UserDataPreparer {
+    private final Object mInstallLock;
+    private final Context mContext;
+    private final boolean mOnlyCore;
+    private final Installer mInstaller;
+
+    UserDataPreparer(Installer installer, Object installLock, Context context, boolean onlyCore) {
+        mInstallLock = installLock;
+        mContext = context;
+        mOnlyCore = onlyCore;
+        mInstaller = installer;
+    }
+
+    /**
+     * Prepare storage areas for given user on all mounted devices.
+     */
+    void prepareUserData(int userId, int userSerial, int flags) {
+        synchronized (mInstallLock) {
+            final StorageManager storage = mContext.getSystemService(StorageManager.class);
+            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+                final String volumeUuid = vol.getFsUuid();
+                prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
+            }
+        }
+    }
+
+    private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags,
+            boolean allowRecover) {
+        // Prepare storage and verify that serial numbers are consistent; if
+        // there's a mismatch we need to destroy to avoid leaking data
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        try {
+            storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
+
+            if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
+                UserManagerService.enforceSerialNumber(
+                        Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial);
+                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
+                    UserManagerService.enforceSerialNumber(
+                            Environment.getDataSystemDeDirectory(userId), userSerial);
+                }
+            }
+            if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
+                UserManagerService.enforceSerialNumber(
+                        Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial);
+                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
+                    UserManagerService.enforceSerialNumber(
+                            Environment.getDataSystemCeDirectory(userId), userSerial);
+                }
+            }
+
+            mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
+        } catch (Exception e) {
+            logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
+                    + " because we failed to prepare: " + e);
+            destroyUserDataLI(volumeUuid, userId,
+                    StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+
+            if (allowRecover) {
+                // Try one last time; if we fail again we're really in trouble
+                prepareUserDataLI(volumeUuid, userId, userSerial, flags, false);
+            }
+        }
+    }
+
+    /**
+     * Destroy storage areas for given user on all mounted devices.
+     */
+    void destroyUserData(int userId, int flags) {
+        synchronized (mInstallLock) {
+            final StorageManager storage = mContext.getSystemService(StorageManager.class);
+            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+                final String volumeUuid = vol.getFsUuid();
+                destroyUserDataLI(volumeUuid, userId, flags);
+            }
+        }
+    }
+
+    void destroyUserDataLI(String volumeUuid, int userId, int flags) {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        try {
+            // Clean up app data, profile data, and media data
+            mInstaller.destroyUserData(volumeUuid, userId, flags);
+
+            // Clean up system data
+            if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
+                if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
+                    FileUtils.deleteContentsAndDir(Environment.getUserSystemDirectory(userId));
+                    FileUtils.deleteContentsAndDir(Environment.getDataSystemDeDirectory(userId));
+                    FileUtils.deleteContentsAndDir(Environment.getDataMiscDeDirectory(userId));
+                }
+                if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
+                    FileUtils.deleteContentsAndDir(Environment.getDataSystemCeDirectory(userId));
+                    FileUtils.deleteContentsAndDir(Environment.getDataMiscCeDirectory(userId));
+                }
+            }
+
+            // Data with special labels is now gone, so finish the job
+            storage.destroyUserStorage(volumeUuid, userId, flags);
+
+        } catch (Exception e) {
+            logCriticalInfo(Log.WARN,
+                    "Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e);
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 9d2d9e5..455d3e4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -227,6 +227,7 @@
     private final Context mContext;
     private final PackageManagerService mPm;
     private final Object mPackagesLock;
+    private final UserDataPreparer mUserDataPreparer;
     // Short-term lock for internal state, when interaction/sync with PM is not required
     private final Object mUsersLock = new Object();
     private final Object mRestrictionsLock = new Object();
@@ -433,7 +434,7 @@
     // TODO b/28848102 Add support for test dependencies injection
     @VisibleForTesting
     UserManagerService(Context context) {
-        this(context, null, new Object(), context.getCacheDir());
+        this(context, null, null, new Object(), context.getCacheDir());
     }
 
     /**
@@ -441,16 +442,18 @@
      * associated with the package manager, and the given lock is the
      * package manager's own lock.
      */
-    UserManagerService(Context context, PackageManagerService pm, Object packagesLock) {
-        this(context, pm, packagesLock, Environment.getDataDirectory());
+    UserManagerService(Context context, PackageManagerService pm, UserDataPreparer userDataPreparer,
+            Object packagesLock) {
+        this(context, pm, userDataPreparer, packagesLock, Environment.getDataDirectory());
     }
 
     private UserManagerService(Context context, PackageManagerService pm,
-            Object packagesLock, File dataDir) {
+            UserDataPreparer userDataPreparer, Object packagesLock, File dataDir) {
         mContext = context;
         mPm = pm;
         mPackagesLock = packagesLock;
         mHandler = new MainHandler();
+        mUserDataPreparer = userDataPreparer;
         synchronized (mPackagesLock) {
             mUsersDir = new File(dataDir, USER_INFO_DIR);
             mUsersDir.mkdirs();
@@ -2494,7 +2497,7 @@
             }
             final StorageManager storage = mContext.getSystemService(StorageManager.class);
             storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
-            mPm.prepareUserData(userId, userInfo.serialNumber,
+            mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
                     StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
             mPm.createNewUser(userId, disallowedPackages);
             userInfo.partial = false;
@@ -2788,7 +2791,7 @@
         mPm.cleanUpUser(this, userHandle);
 
         // Clean up all data before removing metadata
-        mPm.destroyUserData(userHandle,
+        mUserDataPreparer.destroyUserData(userHandle,
                 StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
 
         // Remove this user from the list
@@ -3129,7 +3132,7 @@
         final int userSerial = userInfo.serialNumber;
         // Migrate only if build fingerprints mismatch
         boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint);
-        mPm.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_DE);
+        mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_DE);
         mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_DE, migrateAppsData);
 
         if (userId != UserHandle.USER_SYSTEM) {
@@ -3151,7 +3154,7 @@
         final int userSerial = userInfo.serialNumber;
         // Migrate only if build fingerprints mismatch
         boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint);
-        mPm.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
+        mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
         mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE, migrateAppsData);
     }
 
diff --git a/services/core/java/com/android/server/storage/DiskStatsLoggingService.java b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
index 0a3abf3..7c43162 100644
--- a/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
+++ b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
@@ -21,6 +21,7 @@
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageStats;
 import android.os.AsyncTask;
@@ -28,6 +29,7 @@
 import android.os.Environment;
 import android.os.Environment.UserEnvironment;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -54,7 +56,7 @@
     public boolean onStartJob(JobParameters params) {
         // We need to check the preconditions again because they may not be enforced for
         // subsequent runs.
-        if (!isCharging(this)) {
+        if (!isCharging(this) || !isDumpsysTaskEnabled(getContentResolver())) {
             jobFinished(params, true);
             return false;
         }
@@ -105,6 +107,12 @@
     }
 
     @VisibleForTesting
+    static boolean isDumpsysTaskEnabled(ContentResolver resolver) {
+        // The default is to treat the task as enabled.
+        return Settings.Global.getInt(resolver, Settings.Global.ENABLE_DISKSTATS_LOGGING, 1) != 0;
+    }
+
+    @VisibleForTesting
     static class LogRunnable implements Runnable {
         private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
 
diff --git a/services/core/java/com/android/server/storage/FileCollector.java b/services/core/java/com/android/server/storage/FileCollector.java
index 90f9f139..59cfaf7 100644
--- a/services/core/java/com/android/server/storage/FileCollector.java
+++ b/services/core/java/com/android/server/storage/FileCollector.java
@@ -26,7 +26,6 @@
 import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
 
 /**
  * FileCollector walks over a directory and categorizes storage usage by their type.
@@ -44,8 +43,9 @@
             AUDIO })
     private @interface FileTypes {}
 
-
-    private static final Map<String, Integer> EXTENSION_MAP = new ArrayMap<String, Integer>();
+    // NOTE: If you update these extensions, you'll also want to update
+    // matchgen.py over in installd which is used for non-quota stats.
+    private static final ArrayMap<String, Integer> EXTENSION_MAP = new ArrayMap<>();
     static {
         // Audio
         EXTENSION_MAP.put("aac", AUDIO);
@@ -144,6 +144,36 @@
         EXTENSION_MAP.put("xwd", IMAGES);
     }
 
+    private static File mkdir(File parent, String name) {
+        final File file = new File(parent, name);
+        file.mkdir();
+        return file;
+    }
+
+    /**
+     * Update the mapping used by sdcardfs to map from file extensions to GIDs
+     * used for statistics purposes.
+     */
+    public static void updateKernelExtensions() {
+        final File root = new File("/config/sdcardfs/extensions/");
+        if (!root.exists()) return;
+
+        final File audio = mkdir(root, Integer.toString(android.os.Process.MEDIA_AUDIO_GID));
+        final File video = mkdir(root, Integer.toString(android.os.Process.MEDIA_VIDEO_GID));
+        final File image = mkdir(root, Integer.toString(android.os.Process.MEDIA_IMAGE_GID));
+
+        for (int i = 0; i < EXTENSION_MAP.size(); i++) {
+            final String ext = EXTENSION_MAP.keyAt(i);
+            final int type = EXTENSION_MAP.valueAt(i);
+
+            switch (type) {
+                case AUDIO: mkdir(audio, ext); break;
+                case VIDEO: mkdir(video, ext); break;
+                case IMAGES: mkdir(image, ext); break;
+            }
+        }
+    }
+
     /**
      * Returns the file categorization measurement result.
      * @param path Directory to collect and categorize storage in.
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 27e0f29..3a86874 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -41,6 +41,7 @@
 import android.view.IApplicationToken;
 import android.view.WindowManagerPolicy.StartingSurface;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.AttributeCache;
 /**
  * Controller for the app window token container. This is created by activity manager to link
@@ -202,7 +203,7 @@
                         + " controller=" + taskController);
             }
 
-            atoken = new AppWindowToken(mService, token, voiceInteraction, task.getDisplayContent(),
+            atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
                     inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
                     requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
                     alwaysFocusable, this);
@@ -212,6 +213,18 @@
         }
     }
 
+    @VisibleForTesting
+    AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token,
+            boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
+            boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
+            int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
+            boolean alwaysFocusable, AppWindowContainerController controller) {
+        return  new AppWindowToken(service, token, voiceInteraction, dc,
+                inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
+                rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
+                controller);
+    }
+
     public void removeContainer(int displayId) {
         synchronized(mWindowMap) {
             final DisplayContent dc = mRoot.getDisplayContent(displayId);
@@ -230,6 +243,25 @@
         throw new UnsupportedOperationException("Use removeContainer(displayId) instead.");
     }
 
+    public void reparent(TaskWindowContainerController taskController, int position) {
+        synchronized (mWindowMap) {
+            if (DEBUG_ADD_REMOVE) Slog.i(TAG_WM, "reparent: moving app token=" + mToken
+                    + " to task=" + taskController + " at " + position);
+            if (mContainer == null) {
+                if (DEBUG_ADD_REMOVE) Slog.i(TAG_WM,
+                        "reparent: could not find app token=" + mToken);
+                return;
+            }
+            final Task task = taskController.mContainer;
+            if (task == null) {
+                throw new IllegalArgumentException("reparent: could not find task="
+                        + taskController);
+            }
+            mContainer.reparent(task, position);
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
     public Configuration setOrientation(int requestedOrientation, int displayId,
             Configuration displayConfig, boolean freezeScreenIfNeeded) {
         synchronized(mWindowMap) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 079dc8f..bcc720d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -850,6 +850,27 @@
         }
     }
 
+    void reparent(Task task, int position) {
+        if (task == mTask) {
+            throw new IllegalArgumentException(
+                    "window token=" + this + " already child of task=" + mTask);
+        }
+        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "reParentWindowToken: removing window token=" + this
+                + " from task=" + mTask);
+        final DisplayContent prevDisplayContent = getDisplayContent();
+
+        getParent().removeChild(this);
+        task.addChild(this, position);
+
+        // Relayout display(s).
+        final DisplayContent displayContent = task.getDisplayContent();
+        displayContent.setLayoutNeeded();
+        if (prevDisplayContent != displayContent) {
+            onDisplayChanged(displayContent);
+            prevDisplayContent.setLayoutNeeded();
+        }
+    }
+
     private boolean canFreezeBounds() {
         // For freeform windows, we can't freeze the bounds at the moment because this would make
         // the resizing unresponsive.
diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java
index 2ec2dba..3a6e328 100644
--- a/services/core/java/com/android/server/wm/DimLayerController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -322,6 +322,9 @@
             }
             mState.remove(dimLayerUser);
         }
+        if (mState.isEmpty()) {
+            mSharedFullScreenDimLayer = null;
+        }
     }
 
     @VisibleForTesting
@@ -329,6 +332,11 @@
         return mState.containsKey(dimLayerUser);
     }
 
+    @VisibleForTesting
+    boolean hasSharedFullScreenDimLayer() {
+        return mSharedFullScreenDimLayer != null;
+    }
+
     void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
         applyDim(dimLayerUser, animator, false /* aboveApp */);
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3a74ded..679f178 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -627,6 +627,8 @@
     };
 
     /**
+     * Create new {@link DisplayContent} instance, add itself to the root window container and
+     * initialize direct children.
      * @param display May not be null.
      * @param service You know.
      * @param layersController window layer controller used to assign layer to the windows on this
@@ -661,6 +663,9 @@
         super.addChild(mTaskStackContainers, null);
         super.addChild(mAboveAppWindowsContainers, null);
         super.addChild(mImeWindowsContainers, null);
+
+        // Add itself as a child to the root container.
+        mService.mRoot.addChild(this, null);
     }
 
     int getDisplayId() {
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index aaf724e..5a2ee9a 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -102,12 +102,14 @@
     private int mDividerWindowWidth;
     private int mDividerWindowWidthInactive;
     private int mDividerInsets;
+    private int mTaskHeightInMinimizedMode;
     private boolean mResizing;
     private WindowState mWindow;
     private final Rect mTmpRect = new Rect();
     private final Rect mTmpRect2 = new Rect();
     private final Rect mTmpRect3 = new Rect();
     private final Rect mLastRect = new Rect();
+    private final Rect mMiddlePositionDockedStackRect = new Rect();
     private boolean mLastVisibility = false;
     private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
             = new RemoteCallbackList<>();
@@ -189,6 +191,40 @@
         return (int) (minWidth / mDisplayContent.getDisplayMetrics().density);
     }
 
+    /**
+     * The middlePositionDockedStackRect is half the screen area that sits at the top (portrait) or
+     * left (landscape).
+     *
+     * @return fixed rect for temp stack
+     */
+    Rect getMiddlePositionDockedStackRect() {
+        return mMinimizedDock && isHomeStackResizable() ? mMiddlePositionDockedStackRect : null;
+    }
+
+    void getHomeStackBoundsInDockedMode(Rect outBounds) {
+        final DisplayInfo di = mDisplayContent.getDisplayInfo();
+        mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                mTmpRect);
+        int dividerSize = mDividerWindowWidth - 2 * mDividerInsets;
+        Configuration configuration = mDisplayContent.getConfiguration();
+        if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
+            outBounds.set(0, mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top,
+                    di.logicalWidth, di.logicalHeight);
+        } else {
+            outBounds.set(mTaskHeightInMinimizedMode + dividerSize + mTmpRect.left, 0,
+                    di.logicalWidth, di.logicalHeight);
+        }
+    }
+
+    boolean isHomeStackResizable() {
+        final TaskStack homeStack = mDisplayContent.getHomeStack();
+        if (homeStack == null) {
+            return false;
+        }
+        final Task homeTask = homeStack.findHomeTask();
+        return homeTask != null && homeTask.isResizeable();
+    }
+
     private void initSnapAlgorithmForRotations() {
         final Configuration baseConfig = mDisplayContent.getConfiguration();
 
@@ -228,11 +264,34 @@
                 com.android.internal.R.dimen.docked_stack_divider_insets);
         mDividerWindowWidthInactive = WindowManagerService.dipToPixel(
                 DIVIDER_WIDTH_INACTIVE_DP, mDisplayContent.getDisplayMetrics());
+        mTaskHeightInMinimizedMode = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.task_height_of_minimized_mode);
         initSnapAlgorithmForRotations();
     }
 
+    /**
+     * Calculates the constant rects {@link mMiddlePositionDockedStackRect} based on orientation,
+     * stable insets and display size.
+     */
+    private void updateConstantRects() {
+        final DisplayInfo di = mDisplayContent.getDisplayInfo();
+        mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                mTmpRect);
+        int dividerSize = mDividerWindowWidth - 2 * mDividerInsets;
+        Configuration configuration = mDisplayContent.getConfiguration();
+        boolean isHorizontal = configuration.orientation == Configuration.ORIENTATION_PORTRAIT;
+        int middlePosition = DockedDividerUtils.calculateMiddlePosition(isHorizontal, mTmpRect,
+                di.logicalWidth, di.logicalHeight, dividerSize);
+        if (isHorizontal) {
+            mMiddlePositionDockedStackRect.set(0, 0, di.logicalWidth, middlePosition);
+        } else {
+            mMiddlePositionDockedStackRect.set(0, 0, middlePosition, di.logicalHeight);
+        }
+    }
+
     void onConfigurationChanged() {
         loadDimens();
+        updateConstantRects();
     }
 
     boolean isResizing() {
@@ -412,7 +471,19 @@
         return mImeHideRequested;
     }
 
-    private void notifyDockedStackMinimizedChanged(boolean minimizedDock, long animDuration) {
+    private void notifyDockedStackMinimizedChanged(boolean minimizedDock, boolean animate,
+            boolean isHomeStackResizable) {
+        long animDuration = 0;
+        if (animate) {
+            final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+            final long transitionDuration = isAnimationMaximizing()
+                    ? mService.mAppTransition.getLastClipRevealTransitionDuration()
+                    : DEFAULT_APP_TRANSITION_DURATION;
+            mAnimationDuration = (long)
+                    (transitionDuration * mService.getTransitionAnimationScaleLocked());
+            mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
+            animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction);
+        }
         mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
         mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
                 minimizedDock ? 1 : 0, 0).sendToTarget();
@@ -420,7 +491,8 @@
         for (int i = 0; i < size; ++i) {
             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
             try {
-                listener.onDockedStackMinimizedChanged(minimizedDock, animDuration);
+                listener.onDockedStackMinimizedChanged(minimizedDock, animDuration,
+                        isHomeStackResizable);
             } catch (RemoteException e) {
                 Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e);
             }
@@ -458,7 +530,8 @@
         mDockedStackListeners.register(listener);
         notifyDockedDividerVisibilityChanged(wasVisible());
         notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
-        notifyDockedStackMinimizedChanged(mMinimizedDock, 0 /* animDuration */);
+        notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
+                isHomeStackResizable());
         notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
 
     }
@@ -577,17 +650,23 @@
 
         final boolean imeChanged = clearImeAdjustAnimation();
         boolean minimizedChange = false;
-        if (minimizedDock) {
-            if (animate) {
-                startAdjustAnimation(0f, 1f);
-            } else {
-                minimizedChange |= setMinimizedDockedStack(true);
-            }
+        if (isHomeStackResizable()) {
+            notifyDockedStackMinimizedChanged(minimizedDock, true /* animate */,
+                    true /* isHomeStackResizable */);
+            minimizedChange = true;
         } else {
-            if (animate) {
-                startAdjustAnimation(1f, 0f);
+            if (minimizedDock) {
+                if (animate) {
+                    startAdjustAnimation(0f, 1f);
+                } else {
+                    minimizedChange |= setMinimizedDockedStack(true);
+                }
             } else {
-                minimizedChange |= setMinimizedDockedStack(false);
+                if (animate) {
+                    startAdjustAnimation(1f, 0f);
+                } else {
+                    minimizedChange |= setMinimizedDockedStack(false);
+                }
             }
         }
         if (imeChanged || minimizedChange) {
@@ -688,7 +767,7 @@
 
     private boolean setMinimizedDockedStack(boolean minimized) {
         final TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
-        notifyDockedStackMinimizedChanged(minimized, 0);
+        notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
         return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
     }
 
@@ -742,14 +821,8 @@
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
-            final long transitionDuration = isAnimationMaximizing()
-                    ? mService.mAppTransition.getLastClipRevealTransitionDuration()
-                    : DEFAULT_APP_TRANSITION_DURATION;
-            mAnimationDuration = (long)
-                    (transitionDuration * mService.getTransitionAnimationScaleLocked());
-            mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
-            notifyDockedStackMinimizedChanged(mMinimizedDock,
-                    (long) (mAnimationDuration * mMaximizeMeetFraction));
+            notifyDockedStackMinimizedChanged(mMinimizedDock, true /* animate */,
+                    isHomeStackResizable() /* isHomeStackResizable */);
         }
         float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
         t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator)
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index e039583..5f53d84 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -625,7 +625,7 @@
             final InputChannel inputChannel = w.mInputChannel;
             final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
             if (inputChannel == null || inputWindowHandle == null || w.mRemoved
-                    || w.isAdjustedForMinimizedDock()) {
+                    || w.canReceiveTouchInput()) {
                 // Skip this window because it cannot possibly receive input.
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 22abf30..80e6655 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -213,7 +213,6 @@
         final int displayId = display.getDisplayId();
 
         if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
-        addChild(dc, null);
 
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final Rect rect = new Rect();
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index 9a6f3eb5..e2ea2c5 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -220,13 +220,17 @@
         }
     }
 
-    public void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) {
+    public void getStackDockedModeBounds(Rect outBounds, Rect outTempBounds,
+            Rect outTempInsetBounds, boolean ignoreVisibility) {
         synchronized (mWindowMap) {
             if (mContainer != null) {
-                mContainer.getStackDockedModeBoundsLocked(outBounds, ignoreVisibility);
+                mContainer.getStackDockedModeBoundsLocked(outBounds, outTempBounds,
+                        outTempInsetBounds, ignoreVisibility);
                 return;
             }
             outBounds.setEmpty();
+            outTempBounds.setEmpty();
+            outTempInsetBounds.setEmpty();
         }
     }
 
@@ -269,11 +273,9 @@
         }
     }
 
-    public Rect getBoundsForNewConfiguration() {
+    public void getBoundsForNewConfiguration(Rect outBounds, Rect outTempBounds) {
         synchronized(mWindowMap) {
-            final Rect outBounds = new Rect();
-            mContainer.getBoundsForNewConfiguration(outBounds);
-            return outBounds;
+            mContainer.getBoundsForNewConfiguration(outBounds, outTempBounds);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index d3eae8c..0ff1f0c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -213,7 +213,7 @@
         mAdjustedBounds.set(bounds);
         final boolean adjusted = !mAdjustedBounds.isEmpty();
         Rect insetBounds = null;
-        if (adjusted && isAdjustedForMinimizedDock()) {
+        if (adjusted && isAdjustedForMinimizedDockedStack()) {
             insetBounds = mBounds;
         } else if (adjusted && mAdjustedForIme) {
             if (mImeGoingAway) {
@@ -420,9 +420,14 @@
         return true;
     }
 
-    void getBoundsForNewConfiguration(Rect outBounds) {
+    void getBoundsForNewConfiguration(Rect outBounds, Rect outTempBounds) {
         outBounds.set(mBoundsAfterRotation);
         mBoundsAfterRotation.setEmpty();
+        final DockedStackDividerController controller = getDisplayContent()
+                .mDividerControllerLocked;
+        if (controller.isMinimizedDock() && mStackId == DOCKED_STACK_ID) {
+            outTempBounds.set(controller.getMiddlePositionDockedStackRect());
+        }
     }
 
     /**
@@ -482,7 +487,8 @@
         mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
         final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
                 mService.mContext.getResources(), displayWidth, displayHeight,
-                dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds);
+                dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds,
+                isMinimizedDockAndHomeStackResizable());
         final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
 
         // Recalculate the bounds based on the position of the target.
@@ -675,7 +681,18 @@
         super.onDisplayChanged(dc);
     }
 
-    void getStackDockedModeBoundsLocked(Rect outBounds, boolean ignoreVisibility) {
+    void getStackDockedModeBoundsLocked(Rect outBounds, Rect outTempBounds,
+            Rect outTempInsetBounds, boolean ignoreVisibility) {
+        if (mStackId == HOME_STACK_ID && findHomeTask().isResizeable()) {
+            // Calculate the home stack bounds when in docked mode
+            getDisplayContent().mDividerControllerLocked
+                    .getHomeStackBoundsInDockedMode(outTempBounds);
+            outTempInsetBounds.set(outTempBounds);
+        } else {
+            outTempBounds.setEmpty();
+            outTempInsetBounds.setEmpty();
+        }
+
         if ((mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId))
                 || mDisplayContent == null) {
             outBounds.set(mBounds);
@@ -789,7 +806,9 @@
         mService.mDockedStackCreateBounds = null;
 
         final Rect bounds = new Rect();
-        getStackDockedModeBoundsLocked(bounds, true /*ignoreVisibility*/);
+        final Rect tempBounds = new Rect();
+        final Rect tempInsetBounds = new Rect();
+        getStackDockedModeBoundsLocked(bounds, tempBounds, tempInsetBounds, true /*ignoreVisibility*/);
         getController().requestResize(bounds);
     }
 
@@ -946,8 +965,9 @@
         }
     }
 
-    boolean isAdjustedForMinimizedDock() {
-        return mMinimizeAmount != 0f;
+    boolean shouldIgnoreInput() {
+        return isAdjustedForMinimizedDockedStack() || mStackId == DOCKED_STACK_ID &&
+                isMinimizedDockAndHomeStackResizable();
     }
 
     /**
@@ -1075,6 +1095,11 @@
         return true;
     }
 
+    private boolean isMinimizedDockAndHomeStackResizable() {
+        return mDisplayContent.mDividerControllerLocked.isMinimizedDock()
+                && mDisplayContent.mDividerControllerLocked.isHomeStackResizable();
+    }
+
     /**
      * @return the distance in pixels how much the stack gets minimized from it's original size
      */
@@ -1344,9 +1369,17 @@
              * tasks (including the focused).
              *
              * We save the focused task region once we find it, and add it back at the end.
+             *
+             * If the task is home stack and it is resizable in the minimized state, we want to
+             * exclude the docked stack from touch so we need the entire screen area and not just a
+             * small portion which the home stack currently is resized to.
              */
 
-            task.getDimBounds(mTmpRect);
+            if (task.isHomeTask() && isMinimizedDockAndHomeStackResizable()) {
+                mDisplayContent.getLogicalDisplayRect(mTmpRect);
+            } else {
+                task.getDimBounds(mTmpRect);
+            }
 
             if (task == focusedTask) {
                 // Add the focused task rect back into the exclude region once we are done
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1987f90..d62c62e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4826,6 +4826,14 @@
                 mDisplayMetrics, dw, dh, displayId);
         config.densityDpi = displayInfo.logicalDensityDpi;
 
+        config.colorMode =
+                (displayInfo.isHdr()
+                        ? Configuration.COLOR_MODE_HDR_YES
+                        : Configuration.COLOR_MODE_HDR_NO)
+                | (displayInfo.isWideColorGamut()
+                        ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES
+                        : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO);
+
         // Update the configuration based on available input devices, lid switch,
         // and platform configuration.
         config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 724aab1..050adfe 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1672,11 +1672,6 @@
         return !mLastReportedConfiguration.equals(getConfiguration());
     }
 
-    boolean isAdjustedForMinimizedDock() {
-        return mAppToken != null && mAppToken.mTask != null
-                && mAppToken.mTask.mStack.isAdjustedForMinimizedDock();
-    }
-
     void onWindowReplacementTimeout() {
         if (mWillReplaceWindow) {
             // Since the window already timed out, remove it immediately now.
@@ -2365,7 +2360,13 @@
                 && (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
                 && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
                 && (mAppToken == null || mAppToken.windowsAreFocusable())
-                && !isAdjustedForMinimizedDock();
+                && !canReceiveTouchInput();
+    }
+
+    /** @return true if this window desires touch events. */
+    boolean canReceiveTouchInput() {
+        return mAppToken != null && mAppToken.mTask != null
+                && mAppToken.mTask.mStack.shouldIgnoreInput();
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d30a823..d91b473 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9296,11 +9296,12 @@
         PackageManager packageManager = mInjector.getPackageManager();
 
         UserHandle user = mInjector.binderGetCallingUserHandle();
-        enforceProfileOwnerOrSystemUser(admin);
-        synchronized (this) {
+        if (!isCallerWithSystemUid()) {
             // Ensure the caller is a DO/PO or a permission grant state delegate.
-            enforceCanManageScope(admin, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
-                    DELEGATION_PERMISSION_GRANT);
+            enforceCanManageScope(admin, callerPackage,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, DELEGATION_PERMISSION_GRANT);
+        }
+        synchronized (this) {
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 int granted = mIPackageManager.checkPermission(permission,
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 0ec5f16..e183a31 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -57,14 +57,15 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
 
-// THESE TESTS ARE DISABLED FOR NOW BECAUSE THEY DO NOT FINISH 1/2 THE TIME.
 public class NotificationManagerServiceTest {
+    private static final long WAIT_FOR_IDLE_TIMEOUT = 2;
     private final String pkg = "com.android.server.notification";
     private final int uid = Binder.getCallingUid();
     private NotificationManagerService mNotificationManagerService;
@@ -74,6 +75,7 @@
     private HandlerThread mThread;
 
     @Before
+    @Test
     @UiThreadTest
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
@@ -108,6 +110,9 @@
 
     public void waitForIdle() throws Exception {
         MessageQueue queue = mThread.getLooper().getQueue();
+        if (queue.isIdle()) {
+            return;
+        }
         CountDownLatch latch = new CountDownLatch(1);
         queue.addIdleHandler(new MessageQueue.IdleHandler() {
                 @Override public boolean queueIdle() {
@@ -115,10 +120,10 @@
                     return false;
                 }
         });
-        latch.await();
-        if (!queue.isIdle()) {
-            waitForIdle();
-        }
+        // Timeout is valid in the cases where the queue goes idle before the IdleHandler
+        // is added.
+        latch.await(WAIT_FOR_IDLE_TIMEOUT, TimeUnit.SECONDS);
+        waitForIdle();
     }
 
     private NotificationRecord generateNotificationRecord(NotificationChannel channel) {
@@ -136,6 +141,7 @@
         return new NotificationRecord(mContext, sbn, channel);
     }
 
+    @Test
     @UiThreadTest
     public void testCreateNotificationChannels_SingleChannel() throws Exception {
         final NotificationChannel channel =
@@ -147,6 +153,7 @@
         assertTrue(createdChannel != null);
     }
 
+    @Test
     @UiThreadTest
     public void testCreateNotificationChannels_NullChannelThrowsException() throws Exception {
         try {
@@ -158,6 +165,7 @@
         }
     }
 
+    @Test
     @UiThreadTest
     public void testCreateNotificationChannels_TwoChannels() throws Exception {
         final NotificationChannel channel1 =
@@ -170,6 +178,7 @@
         assertTrue(mBinderService.getNotificationChannel("test_pkg", "id2") != null);
     }
 
+    @Test
     @UiThreadTest
     public void testCreateNotificationChannels_SecondCreateDoesNotChangeImportance()
             throws Exception {
@@ -188,6 +197,7 @@
         assertEquals(NotificationManager.IMPORTANCE_DEFAULT, createdChannel.getImportance());
     }
 
+    @Test
     @UiThreadTest
     public void testCreateNotificationChannels_IdenticalChannelsInListIgnoresSecond()
             throws Exception {
@@ -202,6 +212,7 @@
         assertEquals(NotificationManager.IMPORTANCE_DEFAULT, createdChannel.getImportance());
     }
 
+    @Test
     @UiThreadTest
     public void testBlockedNotifications_suspended() throws Exception {
         NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
@@ -217,6 +228,7 @@
         verify(usageStats, times(1)).registerSuspendedByAdmin(eq(r));
     }
 
+    @Test
     @UiThreadTest
     public void testBlockedNotifications_blockedChannel() throws Exception {
         NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
@@ -233,6 +245,7 @@
         verify(usageStats, times(1)).registerBlocked(eq(r));
     }
 
+    @Test
     @UiThreadTest
     public void testBlockedNotifications_blockedApp() throws Exception {
         NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
@@ -249,6 +262,7 @@
         verify(usageStats, times(1)).registerBlocked(eq(r));
     }
 
+    @Test
     @UiThreadTest
     public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
         mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
@@ -259,6 +273,7 @@
         assertEquals(1, notifs.length);
     }
 
+    @Test
     @UiThreadTest
     public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
         mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
@@ -270,6 +285,7 @@
         assertEquals(0, notifs.length);
     }
 
+    @Test
     @UiThreadTest
     public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -282,6 +298,7 @@
         assertEquals(0, notifs.length);
     }
 
+    @Test
     @UiThreadTest
     public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -294,6 +311,7 @@
         assertEquals(0, notifs.length);
     }
 
+    @Test
     @UiThreadTest
     public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -307,6 +325,7 @@
         assertEquals(1, notifs.length);
     }
 
+    @Test
     @UiThreadTest
     public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -320,6 +339,7 @@
         assertEquals(1, notifs.length);
     }
 
+    @Test
     @UiThreadTest
     public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
@@ -332,6 +352,7 @@
         assertEquals(0, notifs.length);
     }
 
+    @Test
     @UiThreadTest
     public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 5be6e91..b53ec45 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -805,7 +805,7 @@
     public void testCreateGroup() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
         mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
-        assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid, false).getList().get(0));
+        assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid).iterator().next());
     }
 
     @Test
@@ -834,6 +834,8 @@
 
     @Test
     public void testGetChannelGroups() throws Exception {
+        NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
+        mHelper.createNotificationChannelGroup(pkg, uid, unused, true);
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
         mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 677e468..d7bfc44 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -36,6 +36,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutServiceInternal;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -121,6 +122,18 @@
         assertEquals(provider, providerCaptor.getValue().provider);
     }
 
+    public void testIsRequestPinAppWidgetSupported() {
+        ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
+        // Set up users.
+        when(mMockShortcutService.isRequestPinItemSupported(anyInt(), anyInt()))
+                .thenReturn(true, false);
+        assertTrue(mManager.isRequestPinAppWidgetSupported());
+        assertFalse(mManager.isRequestPinAppWidgetSupported());
+
+        verify(mMockShortcutService, times(2)).isRequestPinItemSupported(anyInt(),
+                eq(LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET));
+    }
+
     public void testProviderUpdatesReceived() throws Exception {
         int widgetId = setupHostAndWidget();
         RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
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 c29668f..f4e4e08 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3575,20 +3575,24 @@
 
         // System can retrieve permission grant state.
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        mContext.packageName = "com.example.system";
         assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
                 dpm.getPermissionGrantState(null, app1, permission));
         assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT,
                 dpm.getPermissionGrantState(null, app2, permission));
 
         // A regular app cannot retrieve permission grant state.
-        mMockContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        mContext.binder.callingUid = setupPackageInPackageManager(app1, 1);
+        mContext.packageName = app1;
         try {
             dpm.getPermissionGrantState(null, app1, permission);
-            fail("Didn't throw IllegalStateException");
-        } catch (IllegalStateException expected) {
+            fail("Didn't throw SecurityException");
+        } catch (SecurityException expected) {
         }
 
         // Profile owner can retrieve permission grant state.
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        mContext.packageName = admin1.getPackageName();
         setAsProfileOwner(admin1);
         assertEquals(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
                 dpm.getPermissionGrantState(admin1, app1, permission));
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 9835c88..8c23a91 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -398,7 +398,7 @@
 
         @Override
         ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName,
-                int launcherUserId) {
+                int launcherUserId, int requestType) {
             return mPinConfirmActivityFetcher.apply(launcherPackageName, launcherUserId);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
index 96e8948..0310e16 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
@@ -93,21 +93,24 @@
 
         Pair<ComponentName, Integer> actual;
         // User 0
-        actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_0);
+        actual = mProcessor.getRequestPinConfirmationActivity(USER_0,
+                PinItemRequest.REQUEST_TYPE_SHORTCUT);
 
         assertEquals(LAUNCHER_1, actual.first.getPackageName());
         assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
         assertEquals(USER_0, (int) actual.second);
 
         // User 10
-        actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10);
+        actual = mProcessor.getRequestPinConfirmationActivity(USER_10,
+                PinItemRequest.REQUEST_TYPE_SHORTCUT);
 
         assertEquals(LAUNCHER_2, actual.first.getPackageName());
         assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
         assertEquals(USER_10, (int) actual.second);
 
         // User P0 -> managed profile, return user-0's launcher.
-        actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0);
+        actual = mProcessor.getRequestPinConfirmationActivity(USER_P0,
+                PinItemRequest.REQUEST_TYPE_SHORTCUT);
 
         assertEquals(LAUNCHER_1, actual.first.getPackageName());
         assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
@@ -133,15 +136,18 @@
                         ? null : new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS);
 
         // User 10 -- still has confirm activity.
-        actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10);
+        actual = mProcessor.getRequestPinConfirmationActivity(USER_10,
+                PinItemRequest.REQUEST_TYPE_SHORTCUT);
 
         assertEquals(LAUNCHER_2, actual.first.getPackageName());
         assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
         assertEquals(USER_10, (int) actual.second);
 
         // But user-0 and user p0 no longer has a confirmation activity.
-        assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_0));
-        assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0));
+        assertNull(mProcessor.getRequestPinConfirmationActivity(USER_0,
+                PinItemRequest.REQUEST_TYPE_SHORTCUT));
+        assertNull(mProcessor.getRequestPinConfirmationActivity(USER_P0,
+                PinItemRequest.REQUEST_TYPE_SHORTCUT));
 
         // Check from the public API.
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -204,7 +210,7 @@
     }
 
     private void assertPinItemRequestIntent(Intent actualIntent, String expectedPackage) {
-        assertEquals(LauncherApps.ACTION_CONFIRM_PIN_ITEM, actualIntent.getAction());
+        assertEquals(LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT, actualIntent.getAction());
         assertEquals(expectedPackage, actualIntent.getComponent().getPackageName());
         assertEquals(PIN_CONFIRM_ACTIVITY_CLASS,
                 actualIntent.getComponent().getClassName());
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
index cfa35c2..26033a3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
@@ -72,7 +72,7 @@
     }
 
     private void assertPinItemRequestIntent(Intent actualIntent, String expectedPackage) {
-        assertEquals(LauncherApps.ACTION_CONFIRM_PIN_ITEM, actualIntent.getAction());
+        assertEquals(LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET, actualIntent.getAction());
         assertEquals(expectedPackage, actualIntent.getComponent().getPackageName());
         assertEquals(PIN_CONFIRM_ACTIVITY_CLASS,
                 actualIntent.getComponent().getClassName());
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 2af4163..04e5583 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 /**
  * Test class for {@link AppWindowContainerController}.
@@ -135,9 +136,50 @@
         assertHasStartingWindow(controller2.getAppWindowToken());
     }
 
+    @Test
+    public void testReparent() throws Exception {
+        final TestTaskWindowContainerController taskController1 =
+                new TestTaskWindowContainerController(
+                        createStackControllerOnDisplay(sDisplayContent));
+        final TestAppWindowContainerController appWindowController1 = createAppWindowController(
+                taskController1);
+        final TestTaskWindowContainerController taskController2 =
+                new TestTaskWindowContainerController(
+                        createStackControllerOnDisplay(sDisplayContent));
+        final TestAppWindowContainerController appWindowController2 = createAppWindowController(
+                taskController2);
+        final TestTaskWindowContainerController taskController3 =
+                new TestTaskWindowContainerController(
+                        createStackControllerOnDisplay(sDisplayContent));
+
+        try {
+            appWindowController1.reparent(taskController1, 0);
+            fail("Should not be able to reparent to the same parent");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            taskController3.setContainer(null);
+            appWindowController1.reparent(taskController3, 0);
+            fail("Should not be able to reparent to a task that doesn't have a container");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        // Reparent the app window and ensure that it is moved
+        appWindowController1.reparent(taskController2, 0);
+        assertEquals(taskController2.mContainer, appWindowController1.mContainer.getParent());
+        assertEquals(0, ((TestAppWindowToken) appWindowController1.mContainer).positionInParent());
+        assertEquals(1, ((TestAppWindowToken) appWindowController2.mContainer).positionInParent());
+    }
+
     private TestAppWindowContainerController createAppWindowController() {
-        final TestTaskWindowContainerController taskController =
-                new TestTaskWindowContainerController();
+        return createAppWindowController(new TestTaskWindowContainerController());
+    }
+
+    private TestAppWindowContainerController createAppWindowController(
+            TestTaskWindowContainerController taskController) {
         return new TestAppWindowContainerController(taskController);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java
new file mode 100644
index 0000000..c3a471a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/DimLayerControllerTests.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.hardware.display.DisplayManagerGlobal;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link DimLayerController} class.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.DimLayerControllerTests
+ */
+@SmallTest
+@Presubmit
+@org.junit.runner.RunWith(AndroidJUnit4.class)
+public class DimLayerControllerTests extends WindowTestsBase {
+
+    /**
+     * This tests if shared fullscreen dim layer is added when stack is added to display
+     * and is removed when the only stack on the display is removed.
+     */
+    @Test
+    public void testSharedFullScreenDimLayer() throws Exception {
+        // Create a display.
+        final DisplayContent dc = createNewDisplay();
+        assertFalse(dc.mDimLayerController.hasSharedFullScreenDimLayer());
+
+        // Add stack with activity.
+        final TaskStack stack = createTaskStackOnDisplay(dc);
+        assertTrue(dc.mDimLayerController.hasDimLayerUser(stack));
+        assertTrue(dc.mDimLayerController.hasSharedFullScreenDimLayer());
+
+        // Remove the only stack on the display and check if the shared dim layer clears.
+        stack.removeImmediately();
+        assertFalse(dc.mDimLayerController.hasDimLayerUser(stack));
+        assertFalse(dc.mDimLayerController.hasSharedFullScreenDimLayer());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index e54e319..30f99e5 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -29,6 +29,7 @@
 
 import java.util.ArrayList;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
@@ -215,13 +216,8 @@
      */
     @Test
     public void testMoveStackBetweenDisplays() throws Exception {
-        // Create second display.
-        final Display display = new Display(DisplayManagerGlobal.getInstance(),
-                sDisplayContent.getDisplayId() + 1, new DisplayInfo(),
-                DEFAULT_DISPLAY_ADJUSTMENTS);
-        final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
-                new WallpaperController(sWm));
-        sWm.mRoot.addChild(dc, 1);
+        // Create a second display.
+        final DisplayContent dc = createNewDisplay();
 
         // Add stack with activity.
         final TaskStack stack = createTaskStackOnDisplay(dc);
@@ -261,10 +257,31 @@
 
         // Check that override config is applied.
         assertEquals(newOverrideConfig, sDisplayContent.getOverrideConfiguration());
+    }
+
+    /**
+     * This tests global configuration updates when default display config is updated.
+     */
+    @Test
+    public void testDefaultDisplayOverrideConfigUpdate() throws Exception {
+        final Configuration currentOverrideConfig = sDisplayContent.getOverrideConfiguration();
+
+        // Create new, slightly changed override configuration and apply it to the display.
+        final Configuration newOverrideConfig = new Configuration(currentOverrideConfig);
+        newOverrideConfig.densityDpi += 120;
+        newOverrideConfig.fontScale += 0.3;
+
+        sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, DEFAULT_DISPLAY);
 
         // Check that global configuration is updated, as we've updated default display's config.
-        final Configuration globalConfig = sWm.mRoot.getConfiguration();
+        Configuration globalConfig = sWm.mRoot.getConfiguration();
         assertEquals(newOverrideConfig.densityDpi, globalConfig.densityDpi);
         assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
+
+        // Return back to original values.
+        sWm.setNewDisplayOverrideConfiguration(currentOverrideConfig, DEFAULT_DISPLAY);
+        globalConfig = sWm.mRoot.getConfiguration();
+        assertEquals(currentOverrideConfig.densityDpi, globalConfig.densityDpi);
+        assertEquals(currentOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
index 7a789d4..b0eba0b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
@@ -102,12 +102,7 @@
         task1.mOnDisplayChangedCalled = false;
 
         // Create second display and put second stack on it.
-        final Display display = new Display(DisplayManagerGlobal.getInstance(),
-                sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
-        final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
-                new WallpaperController(sWm));
-        sWm.mRoot.addChild(dc, 1);
-
+        final DisplayContent dc = createNewDisplay();
         final StackWindowController stack2Controller =
                 createStackControllerOnDisplay(dc);
         final TaskStack stack2 = stack2Controller.mContainer;
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
index 3ee1da43..f79908e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
@@ -128,13 +128,10 @@
                 new TestTaskWindowContainerController(stack1Controller);
         final TestTask task1 = (TestTask) taskController.mContainer;
         task1.mOnDisplayChangedCalled = false;
+        assertEquals(sDisplayContent, stack1.getDisplayContent());
 
         // Create second display and put second stack on it.
-        final Display display = new Display(DisplayManagerGlobal.getInstance(),
-                sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
-        final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
-                new WallpaperController(sWm));
-        sWm.mRoot.addChild(dc, 1);
+        final DisplayContent dc = createNewDisplay();
         final StackWindowController stack2Controller = createStackControllerOnDisplay(dc);
         final TaskStack stack2 = stack2Controller.mContainer;
         final TestTaskWindowContainerController taskController2 =
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 72157b6..c6f88ed 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -20,7 +20,10 @@
 import android.app.ActivityManagerInternal;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Binder;
+import android.view.Display;
+import android.view.DisplayInfo;
 import android.view.IApplicationToken;
 import org.junit.Assert;
 import org.junit.Before;
@@ -40,6 +43,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.EMPTY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -65,11 +69,13 @@
     static TestWindowManagerPolicy sPolicy = null;
     private final static IWindow sIWindow = new TestIWindow();
     private final static Session sMockSession = mock(Session.class);
+    private static int sNextDisplayId = Display.DEFAULT_DISPLAY + 1;
     static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
     private static int sNextTaskId = 0;
 
     private static boolean sOneTimeSetupDone = false;
     static DisplayContent sDisplayContent;
+    static DisplayInfo sDisplayInfo = new DisplayInfo();
     static WindowLayersController sLayersController;
     static WindowState sWallpaperWindow;
     static WindowState sImeWindow;
@@ -100,9 +106,15 @@
         if (sDisplayContent != null) {
             sDisplayContent.removeImmediately();
         }
-        sDisplayContent = new DisplayContent(context.getDisplay(), sWm, sLayersController,
-                new WallpaperController(sWm));
-        sWm.mRoot.addChild(sDisplayContent, 0);
+        // Make sure that display ids don't overlap, so there won't be several displays with same
+        // ids among RootWindowContainer children.
+        for (DisplayContent dc : sWm.mRoot.mChildren) {
+            if (dc.getDisplayId() >= sNextDisplayId) {
+                sNextDisplayId = dc.getDisplayId() + 1;
+            }
+        }
+        context.getDisplay().getDisplayInfo(sDisplayInfo);
+        sDisplayContent = createNewDisplay();
         sWm.mDisplayEnabled = true;
         sWm.mDisplayReady = true;
 
@@ -187,7 +199,7 @@
                 true /* onTop */, new Rect(), sWm);
     }
 
-    /**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
+    /** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
     static Task createTaskInStack(TaskStack stack, int userId) {
         final Task newTask = new Task(sNextTaskId++, stack, userId, sWm, null, EMPTY, false, 0,
                 false, false, new TaskDescription(), null);
@@ -195,6 +207,14 @@
         return newTask;
     }
 
+    /** Creates a {@link DisplayContent} and adds it to the system. */
+    DisplayContent createNewDisplay() {
+        final int displayId = sNextDisplayId++;
+        final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
+                sDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
+        return new DisplayContent(display, sWm, sLayersController, new WallpaperController(sWm));
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowToken} class */
     static class TestWindowToken extends WindowToken {
 
@@ -222,6 +242,16 @@
             super(sWm, null, false, dc);
         }
 
+        TestAppWindowToken(WindowManagerService service, IApplicationToken token,
+                boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
+                boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
+                int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
+                boolean alwaysFocusable, AppWindowContainerController controller) {
+            super(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen,
+                    showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges,
+                    launchTaskBehind, alwaysFocusable, controller);
+        }
+
         int getWindowsCount() {
             return mChildren.size();
         }
@@ -237,6 +267,10 @@
         WindowState getLastChild() {
             return mChildren.getLast();
         }
+
+        int positionInParent() {
+            return getParent().mChildren.indexOf(this);
+        }
     }
 
     /* Used so we can gain access to some protected members of the {@link Task} class */
@@ -337,6 +371,19 @@
             mToken = token;
         }
 
+        @Override
+        AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token,
+                boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
+                boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
+                int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
+                boolean alwaysFocusable, AppWindowContainerController controller) {
+            return new TestAppWindowToken(service, token, voiceInteraction, dc,
+                    inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk,
+                    orientation,
+                    rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
+                    controller);
+        }
+
         AppWindowToken getAppWindowToken() {
             return (AppWindowToken) sDisplayContent.getWindowToken(mToken.asBinder());
         }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f53c576..913da82 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -46,8 +47,11 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.ClientRequestStats;
 import android.telephony.TelephonyHistogram;
+import android.telephony.ims.feature.ImsFeature;
 import android.util.Log;
 
+import com.android.ims.internal.IImsServiceController;
+import com.android.ims.internal.IImsServiceFeatureListener;
 import com.android.internal.telecom.ITelecomService;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.IPhoneSubInfo;
@@ -61,6 +65,8 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -2907,6 +2913,30 @@
     }
 
     /**
+     * Send the special dialer code. The IPC caller must be the current default dialer.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     *
+     * @param inputCode The special dialer code to send which follows the format of *#*#<code>#*#*
+     * @return true if sent sucessfully, false otherwise
+     *
+     */
+    public boolean sendDialerCode(String inputCode) {
+        try {
+            final ITelephony telephony = getITelephony();
+            if (telephony == null) {
+                Log.e(TAG, "Telephony service unavailable");
+                return false;
+            }
+            return telephony.sendDialerCode(mContext.getOpPackageName(), inputCode);
+        } catch (RemoteException | NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return false;
+        }
+    }
+
+    /**
      * Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
      * @return the IMPI, or null if not present or not loaded
      * @hide
@@ -4160,6 +4190,37 @@
         }
     }
 
+    /** @hide */
+    @IntDef({ImsFeature.EMERGENCY_MMTEL, ImsFeature.MMTEL, ImsFeature.RCS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Feature {}
+
+    /**
+     * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS
+     * feature or {@link null} if the service is not available. If an ImsServiceController is
+     * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
+     * feature updates.
+     * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for.
+     * @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}.
+     * @param callback Listener that will send updates to ImsManager when there are updates to
+     * ImsServiceController.
+     * @return {@link IImsServiceController} interface for the feature specified or {@link null} if
+     * it is unavailable.
+     * @hide
+     */
+    public IImsServiceController getImsServiceControllerAndListen(int slotId, @Feature int feature,
+            IImsServiceFeatureListener callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getImsServiceControllerAndListen(slotId, feature, callback);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage());
+        }
+        return null;
+    }
+
     /**
      * Set IMS registration state
      *
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index ec419d7..f2b8804 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -33,6 +33,8 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyHistogram;
 import android.telephony.VisualVoicemailSmsFilterSettings;
+import com.android.ims.internal.IImsServiceController;
+import com.android.ims.internal.IImsServiceFeatureListener;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.OperatorInfo;
 
@@ -504,6 +506,9 @@
     oneway void sendVisualVoicemailSmsForSubscriber(in String callingPackage, in int subId,
             in String number, in int port, in String text, in PendingIntent sentIntent);
 
+    // Send the special dialer code. The IPC caller must be the current default dialer.
+    boolean sendDialerCode(String callingPackageName, String inputCode);
+
     /**
      * Returns the network type for data transmission
      * Legacy call, permission-free
@@ -745,6 +750,14 @@
     int getTetherApnRequired();
 
     /**
+     *  Get ImsServiceController binder from ImsResolver that corresponds to the subId and feature
+     *  requested as well as registering the ImsServiceController for callbacks using the
+     *  IImsServiceFeatureListener interface.
+     */
+    IImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
+                IImsServiceFeatureListener callback);
+
+    /**
      * Set the network selection mode to automatic.
      *
      * @param subId the id of the subscription to update.
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index fe4e330..71c9c73 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -149,6 +149,12 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public boolean isPermissionReviewModeEnabled() {
+        return false;
+    }
+
     @Override
     public PermissionGroupInfo getPermissionGroupInfo(String name,
             int flags) throws NameNotFoundException {
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 0681b61..07f7617 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -100,6 +100,15 @@
             </intent-filter>
         </activity>
         <activity
+            android:name=".ClippedListActivity"
+            android:label="General/Clipped ListView"
+            android:theme="@style/NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.uibench.TEST" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name=".TrivialRecyclerViewActivity"
             android:label="General/Trivial RecyclerView" >
             <intent-filter>
diff --git a/tests/UiBench/res/layout/app_bar_navigation_drawer.xml b/tests/UiBench/res/layout/app_bar_navigation_drawer.xml
index ede2a56..5657587 100644
--- a/tests/UiBench/res/layout/app_bar_navigation_drawer.xml
+++ b/tests/UiBench/res/layout/app_bar_navigation_drawer.xml
@@ -16,6 +16,7 @@
   -->
 <android.support.design.widget.CoordinatorLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/app_bar_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
diff --git a/tests/UiBench/src/com/android/test/uibench/ClippedListActivity.java b/tests/UiBench/src/com/android/test/uibench/ClippedListActivity.java
new file mode 100644
index 0000000..7454124
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/ClippedListActivity.java
@@ -0,0 +1,66 @@
+/*
+ * 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.test.uibench;
+
+import android.os.Bundle;
+import android.support.design.widget.NavigationView;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+
+public class ClippedListActivity extends AppCompatActivity
+        implements NavigationView.OnNavigationItemSelectedListener {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_navigation_drawer);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
+                this, drawer, toolbar, R.string.navigation_drawer_open,
+                R.string.navigation_drawer_close);
+        drawer.setDrawerListener(toggle);
+        toggle.syncState();
+
+        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+        navigationView.setNavigationItemSelectedListener(this);
+
+        FragmentManager fm = getSupportFragmentManager();
+        if (fm.findFragmentById(android.R.id.content) == null) {
+            ListFragment listFragment = new ListFragment();
+            ListAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
+                    TextUtils.buildSimpleStringList(40));
+            listFragment.setListAdapter(adapter);
+            fm.beginTransaction().add(R.id.app_bar_layout, listFragment).commit();
+        }
+    }
+
+    @Override
+    public boolean onNavigationItemSelected(MenuItem item) {
+        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+        drawer.closeDrawer(GravityCompat.START);
+        return true;
+    }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/TextUtils.java b/tests/UiBench/src/com/android/test/uibench/TextUtils.java
index d88ca1e..32a5986 100644
--- a/tests/UiBench/src/com/android/test/uibench/TextUtils.java
+++ b/tests/UiBench/src/com/android/test/uibench/TextUtils.java
@@ -35,10 +35,14 @@
     }
 
     public static String[] buildSimpleStringList() {
+        return buildSimpleStringList(SIMPLE_STRING_LENGTH);
+    }
+
+    public static String[] buildSimpleStringList(int stringLength) {
         String[] strings = new String[STRING_COUNT];
         Random random = new Random(0);
         for (int i = 0; i < strings.length; i++) {
-            strings[i] = randomWord(random, SIMPLE_STRING_LENGTH);
+            strings[i] = randomWord(random, stringLength);
         }
         return strings;
     }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 46b6403..2d7a68f 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -68,7 +68,6 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
-import android.test.FlakyTest;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
@@ -154,49 +153,32 @@
     }
 
     /**
-     * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
-     * will return immediately if the handler is already idle.
+     * Block until the given handler becomes idle, or until timeoutMs has passed.
      */
-    private class IdleableHandlerThread extends HandlerThread {
-        private IdleHandler mIdleHandler;
-
-        public IdleableHandlerThread(String name) {
-            super(name);
-        }
-
-        public void waitForIdle(int timeoutMs) {
-            final ConditionVariable cv = new ConditionVariable();
-            final MessageQueue queue = getLooper().getQueue();
-
+    private static void waitForIdleHandler(HandlerThread handler, int timeoutMs) {
+        final ConditionVariable cv = new ConditionVariable();
+        final MessageQueue queue = handler.getLooper().getQueue();
+        final IdleHandler idleHandler = () -> {
             synchronized (queue) {
-                if (queue.isIdle()) {
-                    return;
-                }
-
-                assertNull("BUG: only one idle handler allowed", mIdleHandler);
-                mIdleHandler = new IdleHandler() {
-                    public boolean queueIdle() {
-                        synchronized (queue) {
-                            cv.open();
-                            mIdleHandler = null;
-                            return false;  // Remove the handler.
-                        }
-                    }
-                };
-                queue.addIdleHandler(mIdleHandler);
+                cv.open();
+                return false; // Remove the idleHandler.
             }
-
-            if (!cv.block(timeoutMs)) {
-                fail("HandlerThread " + getName() +
-                        " did not become idle after " + timeoutMs + " ms");
-                queue.removeIdleHandler(mIdleHandler);
+        };
+        synchronized (queue) {
+            if (queue.isIdle()) {
+                return;
             }
+            queue.addIdleHandler(idleHandler);
+        }
+        if (!cv.block(timeoutMs)) {
+            fail("HandlerThread " + handler.getName() +
+                    " did not become idle after " + timeoutMs + " ms");
+            queue.removeIdleHandler(idleHandler);
         }
     }
 
-    // Tests that IdleableHandlerThread works as expected.
     @SmallTest
-    public void testIdleableHandlerThread() {
+    public void testWaitForIdle() {
         final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
 
         // Tests that waitForIdle returns immediately if the service is already idle.
@@ -220,9 +202,9 @@
         }
     }
 
-    @SmallTest
-    @FlakyTest(tolerance = 3)
-    public void testNotWaitingForIdleCausesRaceConditions() {
+    // This test has an inherent race condition in it, and cannot be enabled for continuous testing
+    // or presubmit tests. It is kept for manual runs and documentation purposes.
+    public void verifyThatNotWaitingForIdleCausesRaceConditions() {
         // Bring up a network that we can use to send messages to ConnectivityService.
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -249,7 +231,7 @@
         private final WrappedNetworkMonitor mWrappedNetworkMonitor;
         private final NetworkInfo mNetworkInfo;
         private final NetworkCapabilities mNetworkCapabilities;
-        private final IdleableHandlerThread mHandlerThread;
+        private final HandlerThread mHandlerThread;
         private final ConditionVariable mDisconnected = new ConditionVariable();
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
         private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
@@ -281,7 +263,7 @@
                 default:
                     throw new UnsupportedOperationException("unimplemented network type");
             }
-            mHandlerThread = new IdleableHandlerThread("Mock-" + typeName);
+            mHandlerThread = new HandlerThread("Mock-" + typeName);
             mHandlerThread.start();
             mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
                     "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
@@ -321,7 +303,7 @@
         }
 
         public void waitForIdle(int timeoutMs) {
-            mHandlerThread.waitForIdle(timeoutMs);
+            waitForIdleHandler(mHandlerThread, timeoutMs);
         }
 
         public void waitForIdle() {
@@ -648,11 +630,6 @@
         }
 
         @Override
-        protected HandlerThread createHandlerThread() {
-            return new IdleableHandlerThread("WrappedConnectivityService");
-        }
-
-        @Override
         protected int getDefaultTcpRwnd() {
             // Prevent wrapped ConnectivityService from trying to write to SystemProperties.
             return 0;
@@ -710,7 +687,7 @@
         }
 
         public void waitForIdle(int timeoutMs) {
-            ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs);
+            waitForIdleHandler(mHandlerThread, timeoutMs);
         }
 
         public void waitForIdle() {
@@ -1135,7 +1112,7 @@
         // Chosen to be much less than the linger timeout. This ensures that we can distinguish
         // between a LOST callback that arrives immediately and a LOST callback that arrives after
         // the linger timeout.
-        private final static int TIMEOUT_MS = 50;
+        private final static int TIMEOUT_MS = 100;
 
         private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
 
@@ -1487,8 +1464,8 @@
 
         // Let linger run its course.
         callback.assertNoCallback();
-        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent,
-                TEST_LINGER_DELAY_MS /* timeoutMs */);
+        final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
 
         // Clean up.
         mWiFiNetworkAgent.disconnect();
@@ -1977,7 +1954,9 @@
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
         // When lingering is complete, cell is still there but is now in the background.
-        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, TEST_LINGER_DELAY_MS);
+        mService.waitForIdle();
+        int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
         callback.assertNoCallback();
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -2003,6 +1982,7 @@
 
         // Disconnect wifi and check that cell is foreground again.
         mWiFiNetworkAgent.disconnect();
+        mService.waitForIdle();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
@@ -2339,14 +2319,14 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        mCm.requestNetwork(nr, networkCallback, 10);
+        final int timeoutMs = 150;
+        mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is not called
-        sleepFor(15);
         networkCallback.assertNoCallback();
     }
 
@@ -2359,17 +2339,19 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        mCm.requestNetwork(nr, networkCallback, 500);
+        final int requestTimeoutMs = 100;
+        mCm.requestNetwork(nr, networkCallback, requestTimeoutMs);
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        final int assertTimeoutMs = 150;
+        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, assertTimeoutMs);
         sleepFor(20);
         mWiFiNetworkAgent.disconnect();
         networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // pass timeout and validate that UNAVAILABLE is not called
-        sleepFor(600);
+        sleepFor(100);
         networkCallback.assertNoCallback();
     }
 
@@ -2383,7 +2365,8 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        mCm.requestNetwork(nr, networkCallback, 10);
+        final int timeoutMs = 10;
+        mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is called
         networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
@@ -2403,7 +2386,8 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        mCm.requestNetwork(nr, networkCallback, 10);
+        final int timeoutMs = 10;
+        mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         // remove request
         mCm.unregisterNetworkCallback(networkCallback);
@@ -2420,13 +2404,13 @@
         networkCallback.assertNoCallback();
     }
 
-    public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) throws Exception {
+    public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) {
         long start = SystemClock.elapsedRealtime();
         while (SystemClock.elapsedRealtime() <= start + maxWaitingTimeMs) {
             if (fn.getAsBoolean()) {
                 return;
             }
-            Thread.sleep(10);
+            sleepFor(15);
         }
         assertTrue(fn.getAsBoolean());
     }
@@ -2594,7 +2578,9 @@
         callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
 
         // ... and that stopping it after that has no adverse effects.
-        assertNull(mCm.getNetworkCapabilities(myNet));
+        // TODO: investigate assertEventuallyTrue is needed and waitForIdle() is not enough
+        final Network myNetAlias = myNet;
+        assertEventuallyTrue(() -> mCm.getNetworkCapabilities(myNetAlias) == null, 100);
         ka.stop();
 
         // Reconnect.
@@ -2838,11 +2824,11 @@
     }
 
     /* test utilities */
+    // TODO: eliminate all usages of sleepFor and replace by proper timeouts/waitForIdle.
     static private void sleepFor(int ms) {
         try {
             Thread.sleep(ms);
         } catch (InterruptedException e) {
         }
-
     }
 }
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 1e67769..b8c739b 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -91,12 +91,12 @@
     }
 
     @Test
-    public void testListensForDunNetworks() throws Exception {
+    public void testListensForAllNetworks() throws Exception {
         assertTrue(mCM.listening.isEmpty());
 
         mUNM.start();
         assertFalse(mCM.listening.isEmpty());
-        assertTrue(mCM.isListeningForDun());
+        assertTrue(mCM.isListeningForAll());
 
         mUNM.stop();
         assertTrue(mCM.hasNoCallbacks());
@@ -197,9 +197,12 @@
                    legacyTypeMap.isEmpty();
         }
 
-        boolean isListeningForDun() {
+        boolean isListeningForAll() {
+            final NetworkCapabilities empty = new NetworkCapabilities();
+            empty.clearAll();
+
             for (NetworkRequest req : listening.values()) {
-                if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
+                if (req.networkCapabilities.equalRequestableCapabilities(empty)) {
                     return true;
                 }
             }
diff --git a/tools/incident_report/Android.mk b/tools/incident_report/Android.mk
index 9e56e3d..e57a959 100644
--- a/tools/incident_report/Android.mk
+++ b/tools/incident_report/Android.mk
@@ -34,6 +34,9 @@
     libplatformprotos \
     libprotobuf-cpp-full
 
+# b/34740546, work around clang-tidy segmentation fault.
+LOCAL_TIDY_CHECKS := -modernize*
+
 LOCAL_C_FLAGS := \
     -Wno-unused-parameter
 include $(BUILD_HOST_EXECUTABLE)
diff --git a/tools/incident_section_gen/Android.mk b/tools/incident_section_gen/Android.mk
index acf3f83..0549026 100644
--- a/tools/incident_section_gen/Android.mk
+++ b/tools/incident_section_gen/Android.mk
@@ -21,6 +21,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := incident-section-gen
+# b/34740546, work around clang-tidy segmentation fault.
+LOCAL_TIDY_CHECKS := -modernize*
 LOCAL_CFLAGS += -g -O0
 LOCAL_C_INCLUDES := \
     external/protobuf/src
diff --git a/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java
index 06874bd..8e1f218 100644
--- a/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java
+++ b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java
@@ -16,208 +16,43 @@
 
 package android.view.textservice;
 
-import com.android.internal.textservice.ISpellCheckerSessionListener;
-import com.android.internal.textservice.ITextServicesManager;
-import com.android.internal.textservice.ITextServicesSessionListener;
-
-import android.content.Context;
 import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
 import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
 
 import java.util.Locale;
 
 /**
- * System API to the overall text services, which arbitrates interaction between applications
- * and text services. You can retrieve an instance of this interface with
- * {@link Context#getSystemService(String) Context.getSystemService()}.
- *
- * The user can change the current text services in Settings. And also applications can specify
- * the target text services.
- *
- * <h3>Architecture Overview</h3>
- *
- * <p>There are three primary parties involved in the text services
- * framework (TSF) architecture:</p>
- *
- * <ul>
- * <li> The <strong>text services manager</strong> as expressed by this class
- * is the central point of the system that manages interaction between all
- * other parts.  It is expressed as the client-side API here which exists
- * in each application context and communicates with a global system service
- * that manages the interaction across all processes.
- * <li> A <strong>text service</strong> implements a particular
- * interaction model allowing the client application to retrieve information of text.
- * The system binds to the current text service that is in use, causing it to be created and run.
- * <li> Multiple <strong>client applications</strong> arbitrate with the text service
- * manager for connections to text services.
- * </ul>
- *
- * <h3>Text services sessions</h3>
- * <ul>
- * <li>The <strong>spell checker session</strong> is one of the text services.
- * {@link android.view.textservice.SpellCheckerSession}</li>
- * </ul>
- *
+ * A stub class of TextServicesManager for Layout-Lib.
  */
 public final class TextServicesManager {
-    private static final String TAG = TextServicesManager.class.getSimpleName();
-    private static final boolean DBG = false;
-
-    private static TextServicesManager sInstance;
-
-    private final ITextServicesManager mService;
-
-    private TextServicesManager() {
-        mService = new FakeTextServicesManager();
-    }
+    private static final TextServicesManager sInstance = new TextServicesManager();
+    private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
 
     /**
      * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
      * @hide
      */
     public static TextServicesManager getInstance() {
-        synchronized (TextServicesManager.class) {
-            if (sInstance == null) {
-                sInstance = new TextServicesManager();
-            }
-            return sInstance;
-        }
+        return sInstance;
     }
 
-    /**
-     * Returns the language component of a given locale string.
-     */
-    private static String parseLanguageFromLocaleString(String locale) {
-        final int idx = locale.indexOf('_');
-        if (idx < 0) {
-            return locale;
-        } else {
-            return locale.substring(0, idx);
-        }
-    }
-
-    /**
-     * Get a spell checker session for the specified spell checker
-     * @param locale the locale for the spell checker. If {@code locale} is null and
-     * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
-     * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
-     * the locale specified in Settings will be returned only when it is same as {@code locale}.
-     * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
-     * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
-     * selected.
-     * @param listener a spell checker session lister for getting results from a spell checker.
-     * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
-     * languages in settings will be returned.
-     * @return the spell checker session of the spell checker
-     */
     public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
             SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
-        if (listener == null) {
-            throw new NullPointerException();
-        }
-        if (!referToSpellCheckerLanguageSettings && locale == null) {
-            throw new IllegalArgumentException("Locale should not be null if you don't refer"
-                    + " settings.");
-        }
-
-        if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
-            return null;
-        }
-
-        final SpellCheckerInfo sci;
-        try {
-            sci = mService.getCurrentSpellChecker(null);
-        } catch (RemoteException e) {
-            return null;
-        }
-        if (sci == null) {
-            return null;
-        }
-        SpellCheckerSubtype subtypeInUse = null;
-        if (referToSpellCheckerLanguageSettings) {
-            subtypeInUse = getCurrentSpellCheckerSubtype(true);
-            if (subtypeInUse == null) {
-                return null;
-            }
-            if (locale != null) {
-                final String subtypeLocale = subtypeInUse.getLocale();
-                final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
-                if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
-                    return null;
-                }
-            }
-        } else {
-            final String localeStr = locale.toString();
-            for (int i = 0; i < sci.getSubtypeCount(); ++i) {
-                final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
-                final String tempSubtypeLocale = subtype.getLocale();
-                final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
-                if (tempSubtypeLocale.equals(localeStr)) {
-                    subtypeInUse = subtype;
-                    break;
-                } else if (tempSubtypeLanguage.length() >= 2 &&
-                        locale.getLanguage().equals(tempSubtypeLanguage)) {
-                    subtypeInUse = subtype;
-                }
-            }
-        }
-        if (subtypeInUse == null) {
-            return null;
-        }
-        final SpellCheckerSession session = new SpellCheckerSession(
-                sci, mService, listener, subtypeInUse);
-        try {
-            mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
-                    session.getTextServicesSessionListener(),
-                    session.getSpellCheckerSessionListener(), bundle);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        return session;
+        return null;
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo[] getEnabledSpellCheckers() {
-        try {
-            final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
-            if (DBG) {
-                Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
-            }
-            return retval;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return EMPTY_SPELL_CHECKER_INFO;
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo getCurrentSpellChecker() {
-        try {
-            // Passing null as a locale for ICS
-            return mService.getCurrentSpellChecker(null);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public void setCurrentSpellChecker(SpellCheckerInfo sci) {
-        try {
-            if (sci == null) {
-                throw new NullPointerException("SpellCheckerInfo is null.");
-            }
-            mService.setCurrentSpellChecker(null, sci.getId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return null;
     }
 
     /**
@@ -225,118 +60,13 @@
      */
     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
             boolean allowImplicitlySelectedSubtype) {
-        try {
-            // Passing null as a locale until we support multiple enabled spell checker subtypes.
-            return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public void setSpellCheckerSubtype(SpellCheckerSubtype subtype) {
-        try {
-            final int hashCode;
-            if (subtype == null) {
-                hashCode = 0;
-            } else {
-                hashCode = subtype.hashCode();
-            }
-            mService.setCurrentSpellCheckerSubtype(null, hashCode);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public void setSpellCheckerEnabled(boolean enabled) {
-        try {
-            mService.setSpellCheckerEnabled(enabled);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return null;
     }
 
     /**
      * @hide
      */
     public boolean isSpellCheckerEnabled() {
-        try {
-            return mService.isSpellCheckerEnabled();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return false;
     }
-
-    private static class FakeTextServicesManager implements ITextServicesManager {
-
-        @Override
-        public void finishSpellCheckerService(ISpellCheckerSessionListener arg0)
-                throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        @Override
-        public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1)
-                throws RemoteException {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        @Override
-        public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        @Override
-        public void getSpellCheckerService(String arg0, String arg1,
-                ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4)
-                throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public boolean isSpellCheckerEnabled() throws RemoteException {
-            // TODO Auto-generated method stub
-            return false;
-        }
-
-        @Override
-        public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public void setSpellCheckerEnabled(boolean arg0) throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public IBinder asBinder() {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-    }
-}
\ No newline at end of file
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index d325ee9..de04c43 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -145,6 +145,11 @@
     }
 
     @Override
+    public boolean isPermissionReviewModeEnabled() {
+        return false;
+    }
+
+    @Override
     public PermissionGroupInfo getPermissionGroupInfo(String name, int flags)
             throws NameNotFoundException {
         return null;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e410a9c..0bfb955 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -142,7 +142,7 @@
     private HashMap<String, String> mFields = new HashMap<String, String>();
     private X509Certificate[] mCaCerts;
     private PrivateKey mClientPrivateKey;
-    private X509Certificate mClientCertificate;
+    private X509Certificate[] mClientCertificateChain;
     private int mEapMethod = Eap.NONE;
     private int mPhase2Method = Phase2.NONE;
 
@@ -161,9 +161,19 @@
         for (String key : source.mFields.keySet()) {
             mFields.put(key, source.mFields.get(key));
         }
-        mCaCerts = source.mCaCerts;
+        if (source.mCaCerts != null) {
+            mCaCerts = Arrays.copyOf(source.mCaCerts, source.mCaCerts.length);
+        } else {
+            mCaCerts = null;
+        }
         mClientPrivateKey = source.mClientPrivateKey;
-        mClientCertificate = source.mClientCertificate;
+        if (source.mClientCertificateChain != null) {
+            mClientCertificateChain = Arrays.copyOf(
+                    source.mClientCertificateChain,
+                    source.mClientCertificateChain.length);
+        } else {
+            mClientCertificateChain = null;
+        }
         mEapMethod = source.mEapMethod;
         mPhase2Method = source.mPhase2Method;
     }
@@ -185,7 +195,7 @@
         dest.writeInt(mPhase2Method);
         ParcelUtil.writeCertificates(dest, mCaCerts);
         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
-        ParcelUtil.writeCertificate(dest, mClientCertificate);
+        ParcelUtil.writeCertificates(dest, mClientCertificateChain);
     }
 
     public static final Creator<WifiEnterpriseConfig> CREATOR =
@@ -204,7 +214,7 @@
                     enterpriseConfig.mPhase2Method = in.readInt();
                     enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
                     enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
-                    enterpriseConfig.mClientCertificate = ParcelUtil.readCertificate(in);
+                    enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
                     return enterpriseConfig;
                 }
 
@@ -742,10 +752,54 @@
      * @throws IllegalArgumentException for an invalid key or certificate.
      */
     public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
+        X509Certificate[] clientCertificates = null;
         if (clientCertificate != null) {
-            if (clientCertificate.getBasicConstraints() != -1) {
-                throw new IllegalArgumentException("Cannot be a CA certificate");
+            clientCertificates = new X509Certificate[] {clientCertificate};
+        }
+        setClientKeyEntryWithCertificateChain(privateKey, clientCertificates);
+    }
+
+    /**
+     * Specify a private key and client certificate chain for client authorization.
+     *
+     * <p>A default name is automatically assigned to the key entry and used
+     * with this configuration.  The framework takes care of installing the
+     * key entry when the config is saved and removing the key entry when
+     * the config is removed.
+
+     * @param privateKey
+     * @param clientCertificateChain
+     * @throws IllegalArgumentException for an invalid key or certificate.
+     */
+    public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey,
+            X509Certificate[] clientCertificateChain) {
+        X509Certificate[] newCerts = null;
+        if (clientCertificateChain != null && clientCertificateChain.length > 0) {
+            // We validate that this is a well formed chain that starts
+            // with an end-certificate and is followed by CA certificates.
+            // We don't validate that each following certificate verifies
+            // the previous. https://en.wikipedia.org/wiki/Chain_of_trust
+            //
+            // Basic constraints is an X.509 extension type that defines
+            // whether a given certificate is allowed to sign additional
+            // certificates and what path length restrictions may exist.
+            // We use this to judge whether the certificate is an end
+            // certificate or a CA certificate.
+            // https://cryptography.io/en/latest/x509/reference/
+            if (clientCertificateChain[0].getBasicConstraints() != -1) {
+                throw new IllegalArgumentException(
+                        "First certificate in the chain must be a client end certificate");
             }
+
+            for (int i = 1; i < clientCertificateChain.length; i++) {
+                if (clientCertificateChain[i].getBasicConstraints() == -1) {
+                    throw new IllegalArgumentException(
+                            "All certificates following the first must be CA certificates");
+                }
+            }
+            newCerts = Arrays.copyOf(clientCertificateChain,
+                    clientCertificateChain.length);
+
             if (privateKey == null) {
                 throw new IllegalArgumentException("Client cert without a private key");
             }
@@ -755,7 +809,7 @@
         }
 
         mClientPrivateKey = privateKey;
-        mClientCertificate = clientCertificate;
+        mClientCertificateChain = newCerts;
     }
 
     /**
@@ -764,7 +818,24 @@
      * @return X.509 client certificate
      */
     public X509Certificate getClientCertificate() {
-        return mClientCertificate;
+        if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+            return mClientCertificateChain[0];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the complete client certificate chain
+     *
+     * @return X.509 client certificates
+     */
+    @Nullable public X509Certificate[] getClientCertificateChain() {
+        if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+            return mClientCertificateChain;
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -772,7 +843,7 @@
      */
     public void resetClientKeyEntry() {
         mClientPrivateKey = null;
-        mClientCertificate = null;
+        mClientCertificateChain = null;
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 643753a..ca4d121 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -18,9 +18,19 @@
 
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
 import android.os.Parcel;
 
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Class representing Passpoint configuration.  This contains configurations specified in
  * PerProviderSubscription (PPS) Management Object (MO) tree.
@@ -28,13 +38,108 @@
  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
  * Release 2 Technical Specification.
  *
- * Currently, only HomeSP and Credential subtrees are supported.
- *
  * @hide
  */
 public final class PasspointConfiguration implements Parcelable {
+    private static final String TAG = "PasspointConfiguration";
+
+    /**
+     * Number of bytes for certificate SHA-256 fingerprint byte array.
+     */
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * Maximum bytes for URL string.
+     */
+    private static final int MAX_URL_BYTES = 1023;
+
+    /**
+     * Integer value used for indicating null value in the Parcel.
+     */
+    private static final int NULL_VALUE = -1;
+
     public HomeSP homeSp = null;
     public Credential credential = null;
+    public Policy policy = null;
+
+    /**
+     * Meta data for performing subscription update.
+     */
+    public UpdateParameter subscriptionUpdate = null;
+
+    /**
+     * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256
+     * fingerprint of the certificate.  The certificates are used for verifying AAA server's
+     * identity during EAP authentication.
+     */
+    public Map<String, byte[]> trustRootCertList = null;
+
+    /**
+     * Set by the subscription server, updated every time the configuration is updated by
+     * the subscription server.
+     *
+     * Use Integer.MIN_VALUE to indicate unset value.
+     */
+    public int updateIdentifier = Integer.MIN_VALUE;
+
+    /**
+     * The priority of the credential.
+     *
+     * Use Integer.MIN_VALUE to indicate unset value.
+     */
+    public int credentialPriority = Integer.MIN_VALUE;
+
+    /**
+     * The time this subscription is created. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    public long subscriptionCreationTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * The time this subscription will expire. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    public long subscriptionExpirationTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * The type of the subscription.  This is defined by the provider and the value is provider
+     * specific.
+     */
+    public String subscriptionType = null;
+
+    /**
+     * The time period for usage statistics accumulation. A value of zero means that usage
+     * statistics are not accumulated on a periodic basis (e.g., a one-time limit for
+     * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes.
+     */
+    public long usageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
+
+    /**
+     * The time at which usage statistic accumulation  begins.  It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    public long usageLimitStartTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+     * A value of zero indicate unlimited data usage.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    public long usageLimitDataLimit = Long.MIN_VALUE;
+
+    /**
+     * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+     * A value of zero indicate unlimited time usage.
+     */
+    public long usageLimitTimeLimitInMinutes = Long.MIN_VALUE;
+
 
     /**
      * Constructor for creating PasspointConfiguration with default values.
@@ -47,14 +152,34 @@
      * @param source The source to copy from
      */
     public PasspointConfiguration(PasspointConfiguration source) {
-        if (source != null) {
-            if (source.homeSp != null) {
-                homeSp = new HomeSP(source.homeSp);
-            }
-            if (source.credential != null) {
-                credential = new Credential(source.credential);
-            }
+        if (source == null) {
+            return;
         }
+
+        if (source.homeSp != null) {
+            homeSp = new HomeSP(source.homeSp);
+        }
+        if (source.credential != null) {
+            credential = new Credential(source.credential);
+        }
+        if (source.policy != null) {
+            policy = new Policy(source.policy);
+        }
+        if (source.trustRootCertList != null) {
+            trustRootCertList = Collections.unmodifiableMap(source.trustRootCertList);
+        }
+        if (source.subscriptionUpdate != null) {
+            subscriptionUpdate = new UpdateParameter(source.subscriptionUpdate);
+        }
+        updateIdentifier = source.updateIdentifier;
+        credentialPriority = source.credentialPriority;
+        subscriptionCreationTimeInMs = source.subscriptionCreationTimeInMs;
+        subscriptionExpirationTimeInMs = source.subscriptionExpirationTimeInMs;
+        subscriptionType = source.subscriptionType;
+        usageLimitDataLimit = source.usageLimitDataLimit;
+        usageLimitStartTimeInMs = source.usageLimitStartTimeInMs;
+        usageLimitTimeLimitInMinutes = source.usageLimitTimeLimitInMinutes;
+        usageLimitUsageTimePeriodInMinutes = source.usageLimitUsageTimePeriodInMinutes;
     }
 
     @Override
@@ -66,6 +191,18 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(homeSp, flags);
         dest.writeParcelable(credential, flags);
+        dest.writeParcelable(policy, flags);
+        dest.writeParcelable(subscriptionUpdate, flags);
+        writeTrustRootCerts(dest, trustRootCertList);
+        dest.writeInt(updateIdentifier);
+        dest.writeInt(credentialPriority);
+        dest.writeLong(subscriptionCreationTimeInMs);
+        dest.writeLong(subscriptionExpirationTimeInMs);
+        dest.writeString(subscriptionType);
+        dest.writeLong(usageLimitUsageTimePeriodInMinutes);
+        dest.writeLong(usageLimitStartTimeInMs);
+        dest.writeLong(usageLimitDataLimit);
+        dest.writeLong(usageLimitTimeLimitInMinutes);
     }
 
     @Override
@@ -77,9 +214,22 @@
             return false;
         }
         PasspointConfiguration that = (PasspointConfiguration) thatObject;
-        return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp)) &&
-                (credential == null ? that.credential == null :
-                    credential.equals(that.credential));
+        return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp))
+                && (credential == null ? that.credential == null
+                        : credential.equals(that.credential))
+                && (policy == null) ? that.policy == null : policy.equals(that.policy)
+                && (subscriptionUpdate == null) ? that.subscriptionUpdate == null
+                        : subscriptionUpdate.equals(that.subscriptionUpdate)
+                && isTrustRootCertListEquals(trustRootCertList, that.trustRootCertList)
+                && updateIdentifier == that.updateIdentifier
+                && credentialPriority == that.credentialPriority
+                && subscriptionCreationTimeInMs == that.subscriptionCreationTimeInMs
+                && subscriptionExpirationTimeInMs == that.subscriptionExpirationTimeInMs
+                && TextUtils.equals(subscriptionType, that.subscriptionType)
+                && usageLimitUsageTimePeriodInMinutes == that.usageLimitUsageTimePeriodInMinutes
+                && usageLimitStartTimeInMs == that.usageLimitStartTimeInMs
+                && usageLimitDataLimit == that.usageLimitDataLimit
+                && usageLimitTimeLimitInMinutes == that .usageLimitTimeLimitInMinutes;
     }
 
     /**
@@ -94,6 +244,37 @@
         if (credential == null || !credential.validate()) {
             return false;
         }
+        if (policy != null && !policy.validate()) {
+            return false;
+        }
+        if (subscriptionUpdate != null && !subscriptionUpdate.validate()) {
+            return false;
+        }
+        if (trustRootCertList != null) {
+            for (Map.Entry<String, byte[]> entry : trustRootCertList.entrySet()) {
+                String url = entry.getKey();
+                byte[] certFingerprint = entry.getValue();
+                if (TextUtils.isEmpty(url)) {
+                    Log.d(TAG, "Empty URL");
+                    return false;
+                }
+                if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+                    Log.d(TAG, "URL bytes exceeded the max: "
+                            + url.getBytes(StandardCharsets.UTF_8).length);
+                    return false;
+                }
+
+                if (certFingerprint == null) {
+                    Log.d(TAG, "Fingerprint not specified");
+                    return false;
+                }
+                if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) {
+                    Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+                            + certFingerprint.length);
+                    return false;
+                }
+            }
+        }
         return true;
     }
 
@@ -104,11 +285,88 @@
                 PasspointConfiguration config = new PasspointConfiguration();
                 config.homeSp = in.readParcelable(null);
                 config.credential = in.readParcelable(null);
+                config.policy = in.readParcelable(null);
+                config.subscriptionUpdate = in.readParcelable(null);
+                config.trustRootCertList = readTrustRootCerts(in);
+                config.updateIdentifier = in.readInt();
+                config.credentialPriority = in.readInt();
+                config.subscriptionCreationTimeInMs = in.readLong();
+                config.subscriptionExpirationTimeInMs = in.readLong();
+                config.subscriptionType = in.readString();
+                config.usageLimitUsageTimePeriodInMinutes = in.readLong();
+                config.usageLimitStartTimeInMs = in.readLong();
+                config.usageLimitDataLimit = in.readLong();
+                config.usageLimitTimeLimitInMinutes = in.readLong();
                 return config;
             }
+
             @Override
             public PasspointConfiguration[] newArray(int size) {
                 return new PasspointConfiguration[size];
             }
+
+            /**
+             * Helper function for reading trust root certificate info list from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return The list of trust root certificate URL with the corresponding certificate
+             *         fingerprint
+             */
+            private Map<String, byte[]> readTrustRootCerts(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                Map<String, byte[]> trustRootCerts = new HashMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    String key = in.readString();
+                    byte[] value = in.createByteArray();
+                    trustRootCerts.put(key, value);
+                }
+                return trustRootCerts;
+            }
         };
+
+    /**
+     * Helper function for writing trust root certificate information list.
+     *
+     * @param dest The Parcel to write to
+     * @param trustRootCerts The list of trust root certificate URL with the corresponding
+     *                       certificate fingerprint
+     */
+    private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) {
+        if (trustRootCerts == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(trustRootCerts.size());
+        for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) {
+            dest.writeString(entry.getKey());
+            dest.writeByteArray(entry.getValue());
+        }
+    }
+
+    /**
+     * Helper function for comparing two trust root certificate list.  Cannot use Map#equals
+     * method since the value type (byte[]) doesn't override equals method.
+     *
+     * @param list1 The first trust root certificate list
+     * @param list2 The second trust root certificate list
+     * @return true if the two list are equal
+     */
+    private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1,
+            Map<String, byte[]> list2) {
+        if (list1 == null || list2 == null) {
+            return list1 == list2;
+        }
+        if (list1.size() != list2.size()) {
+            return false;
+        }
+        for (Map.Entry<String, byte[]> entry : list1.entrySet()) {
+            if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
index 98fd0f3..22b0f97 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -19,6 +19,8 @@
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -131,6 +133,20 @@
     private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
 
     /**
+     * Fields under PerProviderSubscription.
+     */
+    private static final String NODE_UPDATE_IDENTIFIER = "UpdateIdentifier";
+    private static final String NODE_AAA_SERVER_TRUST_ROOT = "AAAServerTrustRoot";
+    private static final String NODE_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
+    private static final String NODE_SUBSCRIPTION_PARAMETER = "SubscriptionParameter";
+    private static final String NODE_TYPE_OF_SUBSCRIPTION = "TypeOfSubscription";
+    private static final String NODE_USAGE_LIMITS = "UsageLimits";
+    private static final String NODE_DATA_LIMIT = "DataLimit";
+    private static final String NODE_START_DATE = "StartDate";
+    private static final String NODE_TIME_LIMIT = "TimeLimit";
+    private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
+    private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
+    /**
      * Fields under HomeSP subtree.
      */
     private static final String NODE_HOMESP = "HomeSP";
@@ -168,13 +184,40 @@
     private static final String NODE_INNER_METHOD = "InnerMethod";
     private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
     private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
-    private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256FingerPrint";
+    private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
     private static final String NODE_REALM = "Realm";
     private static final String NODE_SIM = "SIM";
     private static final String NODE_SIM_IMSI = "IMSI";
     private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
 
     /**
+     * Fields under Policy subtree.
+     */
+    private static final String NODE_POLICY = "Policy";
+    private static final String NODE_PREFERRED_ROAMING_PARTNER_LIST =
+            "PreferredRoamingPartnerList";
+    private static final String NODE_FQDN_MATCH = "FQDN_Match";
+    private static final String NODE_PRIORITY = "Priority";
+    private static final String NODE_COUNTRY = "Country";
+    private static final String NODE_MIN_BACKHAUL_THRESHOLD = "MinBackhaulThreshold";
+    private static final String NODE_NETWORK_TYPE = "NetworkType";
+    private static final String NODE_DOWNLINK_BANDWIDTH = "DLBandwidth";
+    private static final String NODE_UPLINK_BANDWIDTH = "ULBandwidth";
+    private static final String NODE_POLICY_UPDATE = "PolicyUpdate";
+    private static final String NODE_UPDATE_INTERVAL = "UpdateInterval";
+    private static final String NODE_UPDATE_METHOD = "UpdateMethod";
+    private static final String NODE_RESTRICTION = "Restriction";
+    private static final String NODE_URI = "URI";
+    private static final String NODE_TRUST_ROOT = "TrustRoot";
+    private static final String NODE_CERT_URL = "CertURL";
+    private static final String NODE_SP_EXCLUSION_LIST = "SPExclusionList";
+    private static final String NODE_REQUIRED_PROTO_PORT_TUPLE = "RequiredProtoPortTuple";
+    private static final String NODE_IP_PROTOCOL = "IPProtocol";
+    private static final String NODE_PORT_NUMBER = "PortNumber";
+    private static final String NODE_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
+    private static final String NODE_OTHER = "Other";
+
+    /**
      * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
      */
     private static final String PPS_MO_URN =
@@ -349,6 +392,10 @@
      *     ...
      *   </RTPProperties>
      *   <Node>
+     *     <NodeName>UpdateIdentifier</NodeName>
+     *     <Value>...</Value>
+     *   </Node>
+     *   <Node>
      *     ...
      *   </Node>
      * </Node>
@@ -361,11 +408,12 @@
             throws ParsingException {
         PasspointConfiguration config = null;
         String nodeName = null;
+        int updateIdentifier = Integer.MIN_VALUE;
         for (XMLNode child : node.getChildren()) {
             switch (child.getTag()) {
                 case TAG_NODE_NAME:
                     if (nodeName != null) {
-                        throw new ParsingException("Duplicant NodeName: " + child.getText());
+                        throw new ParsingException("Duplicate NodeName: " + child.getText());
                     }
                     nodeName = child.getText();
                     if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
@@ -373,13 +421,22 @@
                     }
                     break;
                 case TAG_NODE:
-                    // Only one PerProviderSubscription instance is expected and allowed.
-                    if (config != null) {
-                        throw new ParsingException("Multiple PPS instance");
+                    // A node can be either an UpdateIdentifier node or a PerProviderSubscription
+                    // instance node.  Flatten out the XML tree first by converting it to a PPS
+                    // tree to reduce the complexity of the parsing code.
+                    PPSNode ppsNodeRoot = buildPpsNode(child);
+                    if (TextUtils.equals(ppsNodeRoot.getName(), NODE_UPDATE_IDENTIFIER)) {
+                        if (updateIdentifier != Integer.MIN_VALUE) {
+                            throw new ParsingException("Multiple node for UpdateIdentifier");
+                        }
+                        updateIdentifier = parseInteger(getPpsNodeValue(ppsNodeRoot));
+                    } else {
+                        // Only one PerProviderSubscription instance is expected and allowed.
+                        if (config != null) {
+                            throw new ParsingException("Multiple PPS instance");
+                        }
+                        config = parsePpsInstance(ppsNodeRoot);
                     }
-                    // Convert the XML tree to a PPS tree.
-                    PPSNode ppsInstanceRoot = buildPpsNode(child);
-                    config = parsePpsInstance(ppsInstanceRoot);
                     break;
                 case TAG_RT_PROPERTIES:
                     // Parse and verify URN stored in the RT (Run Time) Properties.
@@ -392,6 +449,9 @@
                     throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
             }
         }
+        if (config != null && updateIdentifier != Integer.MIN_VALUE) {
+            config.updateIdentifier = updateIdentifier;
+        }
         return config;
     }
 
@@ -551,6 +611,21 @@
                 case NODE_CREDENTIAL:
                     config.credential = parseCredential(child);
                     break;
+                case NODE_POLICY:
+                    config.policy = parsePolicy(child);
+                    break;
+                case NODE_AAA_SERVER_TRUST_ROOT:
+                    config.trustRootCertList = parseAAAServerTrustRootList(child);
+                    break;
+                case NODE_SUBSCRIPTION_UPDATE:
+                    config.subscriptionUpdate = parseUpdateParameter(child);
+                    break;
+                case NODE_SUBSCRIPTION_PARAMETER:
+                    parseSubscriptionParameter(child, config);
+                    break;
+                case NODE_CREDENTIAL_PRIORITY:
+                    config.credentialPriority = parseInteger(getPpsNodeValue(child));
+                    break;
                 default:
                     throw new ParsingException("Unknown node: " + child.getName());
             }
@@ -616,11 +691,7 @@
         String[] oiStrArray = oiStr.split(",");
         long[] oiArray = new long[oiStrArray.length];
         for (int i = 0; i < oiStrArray.length; i++) {
-            try {
-                oiArray[i] = Long.parseLong(oiStrArray[i], 16);
-            } catch (NumberFormatException e) {
-                throw new ParsingException("Invalid OI: " + oiStrArray[i]);
-            }
+            oiArray[i] = parseLong(oiStrArray[i], 16);
         }
         return oiArray;
     }
@@ -671,11 +742,7 @@
                     ssid = getPpsNodeValue(child);
                     break;
                 case NODE_HESSID:
-                    try {
-                        hessid = Long.parseLong(getPpsNodeValue(child), 16);
-                    } catch (NumberFormatException e) {
-                        throw new ParsingException("Invalid HESSID: " + getPpsNodeValue(child));
-                    }
+                    hessid = parseLong(getPpsNodeValue(child), 16);
                     break;
                 default:
                     throw new ParsingException("Unknown node under NetworkID instance: " +
@@ -999,6 +1066,503 @@
     }
 
     /**
+     * Parse configurations under PerProviderSubscription/Policy subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Policy subtree
+     * @return {@link Policy}
+     * @throws ParsingException
+     */
+    private static Policy parsePolicy(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for Policy");
+        }
+
+        Policy policy = new Policy();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_PREFERRED_ROAMING_PARTNER_LIST:
+                    policy.preferredRoamingPartnerList = parsePreferredRoamingPartnerList(child);
+                    break;
+                case NODE_MIN_BACKHAUL_THRESHOLD:
+                    parseMinBackhaulThreshold(child, policy);
+                    break;
+                case NODE_POLICY_UPDATE:
+                    policy.policyUpdate = parseUpdateParameter(child);
+                    break;
+                case NODE_SP_EXCLUSION_LIST:
+                    policy.excludedSsidList = parseSpExclusionList(child);
+                    break;
+                case NODE_REQUIRED_PROTO_PORT_TUPLE:
+                    policy.requiredProtoPortMap = parseRequiredProtoPortTuple(child);
+                    break;
+                case NODE_MAXIMUM_BSS_LOAD_VALUE:
+                    policy.maximumBssLoadValue = parseInteger(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under Policy: " + child.getName());
+            }
+        }
+        return policy;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/PreferredRoamingPartnerList subtree
+     * @return List of {@link Policy#RoamingPartner}
+     * @throws ParsingException
+     */
+    private static List<Policy.RoamingPartner> parsePreferredRoamingPartnerList(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for PreferredRoamingPartnerList");
+        }
+        List<Policy.RoamingPartner> partnerList = new ArrayList<>();
+        for (PPSNode child : node.getChildren()) {
+            partnerList.add(parsePreferredRoamingPartner(child));
+        }
+        return partnerList;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+>
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+> subtree
+     * @return {@link Policy#RoamingPartner}
+     * @throws ParsingException
+     */
+    private static Policy.RoamingPartner parsePreferredRoamingPartner(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for PreferredRoamingPartner "
+                    + "instance");
+        }
+
+        Policy.RoamingPartner roamingPartner = new Policy.RoamingPartner();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_FQDN_MATCH:
+                    // FQDN_Match field is in the format of "[FQDN],[MatchInfo]", where [MatchInfo]
+                    // is either "exactMatch" for exact match of FQDN or "includeSubdomains" for
+                    // matching all FQDNs with the same sub-domain.
+                    String fqdnMatch = getPpsNodeValue(child);
+                    String[] fqdnMatchArray = fqdnMatch.split(",");
+                    if (fqdnMatchArray.length != 2) {
+                        throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+                    }
+                    roamingPartner.fqdn = fqdnMatchArray[0];
+                    if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) {
+                        roamingPartner.fqdnExactMatch = true;
+                    } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) {
+                        roamingPartner.fqdnExactMatch = false;
+                    } else {
+                        throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+                    }
+                    break;
+                case NODE_PRIORITY:
+                    roamingPartner.priority = parseInteger(getPpsNodeValue(child));
+                    break;
+                case NODE_COUNTRY:
+                    roamingPartner.countries = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under PreferredRoamingPartnerList "
+                            + "instance " + child.getName());
+            }
+        }
+        return roamingPartner;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+     * into the given policy.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+     * @param policy The policy to store the MinBackhualThreshold configuration
+     * @throws ParsingException
+     */
+    private static void parseMinBackhaulThreshold(PPSNode node, Policy policy)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for MinBackhaulThreshold");
+        }
+        for (PPSNode child : node.getChildren()) {
+            parseMinBackhaulThresholdInstance(child, policy);
+        }
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+     * into the given policy.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+     * @param policy The policy to store the MinBackhaulThreshold configuration
+     * @throws ParsingException
+     */
+    private static void parseMinBackhaulThresholdInstance(PPSNode node, Policy policy)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for MinBackhaulThreshold instance");
+        }
+        String networkType = null;
+        long downlinkBandwidth = Long.MIN_VALUE;
+        long uplinkBandwidth = Long.MIN_VALUE;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_NETWORK_TYPE:
+                    networkType = getPpsNodeValue(child);
+                    break;
+                case NODE_DOWNLINK_BANDWIDTH:
+                    downlinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_UPLINK_BANDWIDTH:
+                    uplinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under MinBackhaulThreshold instance "
+                            + child.getName());
+            }
+        }
+        if (networkType == null) {
+            throw new ParsingException("Missing NetworkType field");
+        }
+
+        if (TextUtils.equals(networkType, "home")) {
+            policy.minHomeDownlinkBandwidth = downlinkBandwidth;
+            policy.minHomeUplinkBandwidth = uplinkBandwidth;
+        } else if (TextUtils.equals(networkType, "roaming")) {
+            policy.minRoamingDownlinkBandwidth = downlinkBandwidth;
+            policy.minRoamingUplinkBandwidth = uplinkBandwidth;
+        } else {
+            throw new ParsingException("Invalid network type: " + networkType);
+        }
+    }
+
+    /**
+     * Parse update parameters. This contained configurations from either
+     * PerProviderSubscription/Policy/PolicyUpdate or PerProviderSubscription/SubscriptionUpdate
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Policy/PolicyUpdate
+     *             or PerProviderSubscription/SubscriptionUpdate subtree
+     * @return {@link UpdateParameter}
+     * @throws ParsingException
+     */
+    private static UpdateParameter parseUpdateParameter(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for Update Parameters");
+        }
+
+        UpdateParameter updateParam = new UpdateParameter();
+        for (PPSNode child : node.getChildren()) {
+            switch(child.getName()) {
+                case NODE_UPDATE_INTERVAL:
+                    updateParam.updateIntervalInMinutes = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_UPDATE_METHOD:
+                    updateParam.updateMethod = getPpsNodeValue(child);
+                    break;
+                case NODE_RESTRICTION:
+                    updateParam.restriction = getPpsNodeValue(child);
+                    break;
+                case NODE_URI:
+                    updateParam.serverUri = getPpsNodeValue(child);
+                    break;
+                case NODE_USERNAME_PASSWORD:
+                    Pair<String, String> usernamePassword = parseUpdateUserCredential(child);
+                    updateParam.username = usernamePassword.first;
+                    updateParam.base64EncodedPassword = usernamePassword.second;
+                    break;
+                case NODE_TRUST_ROOT:
+                    Pair<String, byte[]> trustRoot = parseTrustRoot(child);
+                    updateParam.trustRootCertUrl = trustRoot.first;
+                    updateParam.trustRootCertSha256Fingerprint = trustRoot.second;
+                    break;
+                case NODE_OTHER:
+                    Log.d(TAG, "Ignore unsupported paramter: " + child.getName());
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under Update Parameters: "
+                            + child.getName());
+            }
+        }
+        return updateParam;
+    }
+
+    /**
+     * Parse username and password parameters associated with policy or subscription update.
+     * This contained configurations under either
+     * PerProviderSubscription/Policy/PolicyUpdate/UsernamePassword or
+     * PerProviderSubscription/SubscriptionUpdate/UsernamePassword subtree.
+     *
+     * @param node PPSNode representing the root of the UsernamePassword subtree
+     * @return Pair of username and password
+     * @throws ParsingException
+     */
+    private static Pair<String, String> parseUpdateUserCredential(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for UsernamePassword");
+        }
+
+        String username = null;
+        String password = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_USERNAME:
+                    username = getPpsNodeValue(child);
+                    break;
+                case NODE_PASSWORD:
+                    password = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under UsernamePassword: "
+                            + child.getName());
+            }
+        }
+        return Pair.create(username, password);
+    }
+
+    /**
+     * Parse the trust root parameters associated with policy update, subscription update, or AAA
+     * server trust root.
+     *
+     * This contained configurations under either
+     * PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or
+     * PerProviderSubscription/SubscriptionUpdate/TrustRoot or
+     * PerProviderSubscription/AAAServerTrustRoot/<X+> subtree.
+     *
+     * @param node PPSNode representing the root of the TrustRoot subtree
+     * @return Pair of Certificate URL and fingerprint
+     * @throws ParsingException
+     */
+    private static Pair<String, byte[]> parseTrustRoot(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for TrustRoot");
+        }
+
+        String certUrl = null;
+        byte[] certFingerprint = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_CERT_URL:
+                    certUrl = getPpsNodeValue(child);
+                    break;
+                case NODE_CERT_SHA256_FINGERPRINT:
+                    certFingerprint = parseHexString(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under TrustRoot: "
+                            + child.getName());
+            }
+        }
+        return Pair.create(certUrl, certFingerprint);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/SPExclusionList subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/SPExclusionList subtree
+     * @return Array of excluded SSIDs
+     * @throws ParsingException
+     */
+    private static String[] parseSpExclusionList(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SPExclusionList");
+        }
+        List<String> ssidList = new ArrayList<>();
+        for (PPSNode child : node.getChildren()) {
+            ssidList.add(parseSpExclusionInstance(child));
+        }
+        return ssidList.toArray(new String[ssidList.size()]);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/SPExclusionList/<X+> subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/SPExclusionList/<X+> subtree
+     * @return String
+     * @throws ParsingException
+     */
+    private static String parseSpExclusionInstance(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SPExclusion instance");
+        }
+        String ssid = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_SSID:
+                    ssid = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under SPExclusion instance");
+            }
+        }
+        return ssid;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/RequiredProtoPortTuple subtree
+     * @return Map of IP Protocol to Port Number tuples
+     * @throws ParsingException
+     */
+    private static Map<Integer, String> parseRequiredProtoPortTuple(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple");
+        }
+        Map<Integer, String> protoPortTupleMap = new HashMap<>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<Integer, String> protoPortTuple = parseProtoPortTuple(child);
+            protoPortTupleMap.put(protoPortTuple.first, protoPortTuple.second);
+        }
+        return protoPortTupleMap;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+>
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+> subtree
+     * @return Pair of IP Protocol to Port Number tuple
+     * @throws ParsingException
+     */
+    private static Pair<Integer, String> parseProtoPortTuple(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple "
+                    + "instance");
+        }
+        int proto = Integer.MIN_VALUE;
+        String ports = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_IP_PROTOCOL:
+                    proto = parseInteger(getPpsNodeValue(child));
+                    break;
+                case NODE_PORT_NUMBER:
+                    ports = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under RequiredProtoPortTuple instance"
+                            + child.getName());
+            }
+        }
+        if (proto == Integer.MIN_VALUE) {
+            throw new ParsingException("Missing IPProtocol field");
+        }
+        if (ports == null) {
+            throw new ParsingException("Missing PortNumber field");
+        }
+        return Pair.create(proto, ports);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/AAAServerTrustRoot subtree.
+     *
+     * @param node PPSNode representing the root of PerProviderSubscription/AAAServerTrustRoot
+     *             subtree
+     * @return Map of certificate URL with the corresponding certificate fingerprint
+     * @throws ParsingException
+     */
+    private static Map<String, byte[]> parseAAAServerTrustRootList(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for AAAServerTrustRoot");
+        }
+        Map<String, byte[]> certList = new HashMap<>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<String, byte[]> certTuple = parseTrustRoot(child);
+            certList.put(certTuple.first, certTuple.second);
+        }
+        return certList;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/SubscriptionParameter subtree.
+     *
+     * @param node PPSNode representing the root of PerProviderSubscription/SubscriptionParameter
+     *             subtree
+     * @param config Instance of {@link PasspointConfiguration}
+     * @throws ParsingException
+     */
+    private static void parseSubscriptionParameter(PPSNode node, PasspointConfiguration config)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SubscriptionParameter");
+        }
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_CREATION_DATE:
+                    config.subscriptionCreationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_EXPIRATION_DATE:
+                    config.subscriptionExpirationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_TYPE_OF_SUBSCRIPTION:
+                    config.subscriptionType = getPpsNodeValue(child);
+                    break;
+                case NODE_USAGE_LIMITS:
+                    parseUsageLimits(child, config);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under SubscriptionParameter"
+                            + child.getName());
+            }
+        }
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/SubscriptionParameter/UsageLimits
+     * subtree.
+     *
+     * @param node PPSNode representing the root of
+     *             PerProviderSubscription/SubscriptionParameter/UsageLimits subtree
+     * @param config Instance of {@link PasspointConfiguration}
+     * @throws ParsingException
+     */
+    private static void parseUsageLimits(PPSNode node, PasspointConfiguration config)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for UsageLimits");
+        }
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_DATA_LIMIT:
+                    config.usageLimitDataLimit = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_START_DATE:
+                    config.usageLimitStartTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_TIME_LIMIT:
+                    config.usageLimitTimeLimitInMinutes = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_USAGE_TIME_PERIOD:
+                    config.usageLimitUsageTimePeriodInMinutes =
+                            parseLong(getPpsNodeValue(child), 10);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under UsageLimits"
+                            + child.getName());
+            }
+        }
+    }
+
+    /**
      * Convert a hex string to a byte array.
      *
      * @param str String containing hex values
@@ -1054,6 +1618,21 @@
     }
 
     /**
+     * Parse a string representing a long integer.
+     *
+     * @param value String of long integer value
+     * @return long
+     * @throws ParsingException
+     */
+    private static long parseLong(String value, int radix) throws ParsingException {
+        try {
+            return Long.parseLong(value, radix);
+        } catch (NumberFormatException e) {
+            throw new ParsingException("Invalid long integer value: " + value);
+        }
+    }
+
+    /**
      * Convert a List<Long> to a primitive long array long[].
      *
      * @param list List to be converted
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
new file mode 100644
index 0000000..e923f1f
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.wifi.hotspot2.pps;
+
+parcelable Policy;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
new file mode 100644
index 0000000..b2583d3
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
@@ -0,0 +1,452 @@
+/**
+ * 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.wifi.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class representing Policy subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * The Policy specifies additional criteria for Passpoint network selections, such as preferred
+ * roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
+ * updating the policy.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class Policy implements Parcelable {
+    private static final String TAG = "Policy";
+
+    /**
+     * Default priority for preferred roaming partner.
+     */
+    public static final int PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY = 128;
+
+    /**
+     * Maximum number of SSIDs in the exclusion list.
+     */
+    private static final int MAX_EXCLUSION_SSIDS = 128;
+
+    /**
+     * Maximum byte for SSID.
+     */
+    private static final int MAX_SSID_BYTES = 32;
+
+    /**
+     * Maximum bytes for port string in {@link #requiredProtoPortMap}.
+     */
+    private static final int MAX_PORT_STRING_BYTES = 64;
+
+    /**
+     * Integer value used for indicating null value in the Parcel.
+     */
+    private static final int NULL_VALUE = -1;
+
+    /**
+     * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+     * selecting a network from home providers.
+     *
+     * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+     * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long minHomeDownlinkBandwidth = Long.MIN_VALUE;
+    public long minHomeUplinkBandwidth = Long.MIN_VALUE;
+
+    /**
+     * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+     * selecting a network from roaming providers.
+     *
+     * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+     * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long minRoamingDownlinkBandwidth = Long.MIN_VALUE;
+    public long minRoamingUplinkBandwidth = Long.MIN_VALUE;
+
+    /**
+     * List of SSIDs that are not preferred by the Home SP.
+     */
+    public String[] excludedSsidList = null;
+
+    /**
+     * List of IP protocol and port number required by one or more operator supported application.
+     * The port string contained one or more port numbers delimited by ",".
+     */
+    public Map<Integer, String> requiredProtoPortMap = null;
+
+    /**
+     * This specifies the maximum acceptable BSS load policy.  This is used to prevent device
+     * from joining an AP whose channel is overly congested with traffic.
+     * Using Integer.MIN_VALUE to indicate unset value.
+     */
+    public int maximumBssLoadValue = Integer.MIN_VALUE;
+
+    /**
+     * Policy associated with a roaming provider.  This specifies a priority associated
+     * with a roaming provider for given list of countries.
+     *
+     * Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
+     */
+    public static final class RoamingPartner implements Parcelable {
+        /**
+         * FQDN of the roaming partner.
+         */
+        public String fqdn = null;
+
+        /**
+         * Flag indicating the exact match of FQDN is required for FQDN matching.
+         *
+         * When this flag is set to false, sub-domain matching is used.  For example, when
+         * {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
+         */
+        public boolean fqdnExactMatch = false;
+
+        /**
+         * Priority associated with this roaming partner policy.
+         */
+        public int priority = PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY;
+
+        /**
+         * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
+         * character country strings or the country-independent value, "*".
+         */
+        public String countries = null;
+
+        public RoamingPartner() {}
+
+        public RoamingPartner(RoamingPartner source) {
+            if (source != null) {
+                fqdn = source.fqdn;
+                fqdnExactMatch = source.fqdnExactMatch;
+                priority = source.priority;
+                countries = source.countries;
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(fqdn);
+            dest.writeInt(fqdnExactMatch ? 1 : 0);
+            dest.writeInt(priority);
+            dest.writeString(countries);
+        }
+
+        @Override
+        public boolean equals(Object thatObject) {
+            if (this == thatObject) {
+                return true;
+            }
+            if (!(thatObject instanceof RoamingPartner)) {
+                return false;
+            }
+
+            RoamingPartner that = (RoamingPartner) thatObject;
+            return TextUtils.equals(fqdn, that.fqdn)
+                    && fqdnExactMatch == that.fqdnExactMatch
+                    && priority == that.priority
+                    && TextUtils.equals(countries, that.countries);
+        }
+
+        /**
+         * Validate RoamingParnter data.
+         *
+         * @return true on success
+         */
+        public boolean validate() {
+            if (TextUtils.isEmpty(fqdn)) {
+                Log.d(TAG, "Missing FQDN");
+                return false;
+            }
+            if (TextUtils.isEmpty(countries)) {
+                Log.d(TAG, "Missing countries");
+                return false;
+            }
+            return true;
+        }
+
+        public static final Creator<RoamingPartner> CREATOR =
+            new Creator<RoamingPartner>() {
+                @Override
+                public RoamingPartner createFromParcel(Parcel in) {
+                    RoamingPartner roamingPartner = new RoamingPartner();
+                    roamingPartner.fqdn = in.readString();
+                    roamingPartner.fqdnExactMatch = in.readInt() != 0;
+                    roamingPartner.priority = in.readInt();
+                    roamingPartner.countries = in.readString();
+                    return roamingPartner;
+                }
+
+                @Override
+                public RoamingPartner[] newArray(int size) {
+                    return new RoamingPartner[size];
+                }
+            };
+    }
+    public List<RoamingPartner> preferredRoamingPartnerList = null;
+
+    /**
+     * Meta data used for policy update.
+     */
+    public UpdateParameter policyUpdate = null;
+
+    /**
+     * Constructor for creating Policy with default values.
+     */
+    public Policy() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public Policy(Policy source) {
+        if (source == null) {
+            return;
+        }
+        minHomeDownlinkBandwidth = source.minHomeDownlinkBandwidth;
+        minHomeUplinkBandwidth = source.minHomeUplinkBandwidth;
+        minRoamingDownlinkBandwidth = source.minRoamingDownlinkBandwidth;
+        minRoamingUplinkBandwidth = source.minRoamingUplinkBandwidth;
+        maximumBssLoadValue = source.maximumBssLoadValue;
+        if (source.excludedSsidList != null) {
+            excludedSsidList = Arrays.copyOf(source.excludedSsidList,
+                    source.excludedSsidList.length);
+        }
+        if (source.requiredProtoPortMap != null) {
+            requiredProtoPortMap = Collections.unmodifiableMap(source.requiredProtoPortMap);
+        }
+        if (source.preferredRoamingPartnerList != null) {
+            preferredRoamingPartnerList = Collections.unmodifiableList(
+                    source.preferredRoamingPartnerList);
+        }
+        if (source.policyUpdate != null) {
+            policyUpdate = new UpdateParameter(source.policyUpdate);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(minHomeDownlinkBandwidth);
+        dest.writeLong(minHomeUplinkBandwidth);
+        dest.writeLong(minRoamingDownlinkBandwidth);
+        dest.writeLong(minRoamingUplinkBandwidth);
+        dest.writeStringArray(excludedSsidList);
+        writeProtoPortMap(dest, requiredProtoPortMap);
+        dest.writeInt(maximumBssLoadValue);
+        writeRoamingPartnerList(dest, flags, preferredRoamingPartnerList);
+        dest.writeParcelable(policyUpdate, flags);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof Policy)) {
+            return false;
+        }
+        Policy that = (Policy) thatObject;
+
+        return minHomeDownlinkBandwidth == that.minHomeDownlinkBandwidth
+                && minHomeUplinkBandwidth == that.minHomeUplinkBandwidth
+                && minRoamingDownlinkBandwidth == that.minRoamingDownlinkBandwidth
+                && minRoamingUplinkBandwidth == that.minRoamingUplinkBandwidth
+                && Arrays.equals(excludedSsidList, that.excludedSsidList)
+                && (requiredProtoPortMap == null) ? that.requiredProtoPortMap == null
+                        : requiredProtoPortMap.equals(that.requiredProtoPortMap)
+                && maximumBssLoadValue == that.maximumBssLoadValue
+                && (preferredRoamingPartnerList == null) ? that.preferredRoamingPartnerList == null
+                        : preferredRoamingPartnerList.equals(that.preferredRoamingPartnerList)
+                && (policyUpdate == null) ? that.policyUpdate == null
+                        : policyUpdate.equals(that.policyUpdate);
+    }
+
+    /**
+     * Validate Policy data.
+     *
+     * @return true on success
+     */
+    public boolean validate() {
+        if (policyUpdate == null) {
+            Log.d(TAG, "PolicyUpdate not specified");
+            return false;
+        }
+        if (!policyUpdate.validate()) {
+            return false;
+        }
+
+        // Validate SSID exclusion list.
+        if (excludedSsidList != null) {
+            if (excludedSsidList.length > MAX_EXCLUSION_SSIDS) {
+                Log.d(TAG, "SSID exclusion list size exceeded the max: "
+                        + excludedSsidList.length);
+                return false;
+            }
+            for (String ssid : excludedSsidList) {
+                if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+                    Log.d(TAG, "Invalid SSID: " + ssid);
+                    return false;
+                }
+            }
+        }
+        // Validate required protocol to port map.
+        if (requiredProtoPortMap != null) {
+            for (Map.Entry<Integer, String> entry : requiredProtoPortMap.entrySet()) {
+                String portNumber = entry.getValue();
+                if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
+                    Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
+                    return false;
+                }
+            }
+        }
+        // Validate preferred roaming partner list.
+        if (preferredRoamingPartnerList != null) {
+            for (RoamingPartner partner : preferredRoamingPartnerList) {
+                if (!partner.validate()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public static final Creator<Policy> CREATOR =
+        new Creator<Policy>() {
+            @Override
+            public Policy createFromParcel(Parcel in) {
+                Policy policy = new Policy();
+                policy.minHomeDownlinkBandwidth = in.readLong();
+                policy.minHomeUplinkBandwidth = in.readLong();
+                policy.minRoamingDownlinkBandwidth = in.readLong();
+                policy.minRoamingUplinkBandwidth = in.readLong();
+                policy.excludedSsidList = in.createStringArray();
+                policy.requiredProtoPortMap = readProtoPortMap(in);
+                policy.maximumBssLoadValue = in.readInt();
+                policy.preferredRoamingPartnerList = readRoamingPartnerList(in);
+                policy.policyUpdate = in.readParcelable(null);
+                return policy;
+            }
+
+            @Override
+            public Policy[] newArray(int size) {
+                return new Policy[size];
+            }
+
+            /**
+             * Helper function for reading IP Protocol to Port Number map from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return Map of IP protocol to port number
+             */
+            private Map<Integer, String> readProtoPortMap(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                Map<Integer, String> protoPortMap = new HashMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    int key = in.readInt();
+                    String value = in.readString();
+                    protoPortMap.put(key, value);
+                }
+                return protoPortMap;
+            }
+
+            /**
+             * Helper function for reading roaming partner list from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return List of roaming partners
+             */
+            private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                List<RoamingPartner> partnerList = new ArrayList<>();
+                for (int i = 0; i < size; i++) {
+                    partnerList.add(in.readParcelable(null));
+                }
+                return partnerList;
+            }
+
+        };
+
+    /**
+     * Helper function for writing IP Protocol to Port Number map to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param protoPortMap The map to write
+     */
+    private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
+        if (protoPortMap == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(protoPortMap.size());
+        for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
+            dest.writeInt(entry.getKey());
+            dest.writeString(entry.getValue());
+        }
+    }
+
+    /**
+     * Helper function for writing roaming partner list to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param flags The flag about how the object should be written
+     * @param partnerList The partner list to write
+     */
+    private static void writeRoamingPartnerList(Parcel dest, int flags,
+            List<RoamingPartner> partnerList) {
+        if (partnerList == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(partnerList.size());
+        for (RoamingPartner partner : partnerList) {
+            dest.writeParcelable(partner, flags);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
new file mode 100644
index 0000000..701db47
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.wifi.hotspot2.pps;
+
+parcelable UpdateParameter;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
new file mode 100644
index 0000000..a390df7
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
@@ -0,0 +1,303 @@
+/**
+ * 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.wifi.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Class representing configuration parameters for subscription or policy update in
+ * PerProviderSubscription (PPS) Management Object (MO) tree.  This is used by both
+ * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class UpdateParameter implements Parcelable {
+    private static final String TAG = "UpdateParameter";
+
+    /**
+     * Value indicating policy update is not applicable.  Thus, never check with policy server
+     * for updates.
+     */
+    public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL;
+
+    /**
+     * Valid string for UpdateMethod.
+     */
+    public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+    public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+
+    /**
+     * Valid string for Restriction.
+     */
+    public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+    public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+    public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+
+    /**
+     * Maximum bytes for URI string.
+     */
+    private static final int MAX_URI_BYTES = 1023;
+
+    /**
+     * Maximum bytes for URI string.
+     */
+    private static final int MAX_URL_BYTES = 1023;
+
+    /**
+     * Maximum bytes for username.
+     */
+    private static final int MAX_USERNAME_BYTES = 63;
+
+    /**
+     * Maximum bytes for password.
+     */
+    private static final int MAX_PASSWORD_BYTES = 255;
+
+    /**
+     * Number of bytes for certificate SHA-256 fingerprint byte array.
+     */
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * This specifies how often the mobile device shall check with policy server for updates.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long updateIntervalInMinutes = Long.MIN_VALUE;
+
+    /**
+     * The method used to update the policy.  Permitted values are "OMA-DM-ClientInitiated"
+     * and "SPP-ClientInitiated".
+     */
+    public String updateMethod = null;
+
+    /**
+     * This specifies the hotspots at which the subscription update is permitted.  Permitted
+     * values are "HomeSP", "RoamingPartner", or "Unrestricted";
+     */
+    public String restriction = null;
+
+    /**
+     * The URI of the update server.
+     */
+    public String serverUri = null;
+
+    /**
+     * Username used to authenticate with the policy server.
+     */
+    public String username = null;
+
+    /**
+     * Base64 encoded password used to authenticate with the policy server.
+     */
+    public String base64EncodedPassword = null;
+
+    /**
+     * HTTPS URL for retrieving certificate for trust root.  The trust root is used to validate
+     * policy server's identity.
+     */
+    public String trustRootCertUrl = null;
+
+    /**
+     * SHA-256 fingerprint of the certificate located at {@link #trustRootCertUrl}
+     */
+    public byte[] trustRootCertSha256Fingerprint = null;
+
+    /**
+     * Constructor for creating Policy with default values.
+     */
+    public UpdateParameter() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public UpdateParameter(UpdateParameter source) {
+        if (source == null) {
+            return;
+        }
+        updateIntervalInMinutes = source.updateIntervalInMinutes;
+        updateMethod = source.updateMethod;
+        restriction = source.restriction;
+        serverUri = source.serverUri;
+        username = source.username;
+        base64EncodedPassword = source.base64EncodedPassword;
+        trustRootCertUrl = source.trustRootCertUrl;
+        if (source.trustRootCertSha256Fingerprint != null) {
+            trustRootCertSha256Fingerprint = Arrays.copyOf(source.trustRootCertSha256Fingerprint,
+                    source.trustRootCertSha256Fingerprint.length);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(updateIntervalInMinutes);
+        dest.writeString(updateMethod);
+        dest.writeString(restriction);
+        dest.writeString(serverUri);
+        dest.writeString(username);
+        dest.writeString(base64EncodedPassword);
+        dest.writeString(trustRootCertUrl);
+        dest.writeByteArray(trustRootCertSha256Fingerprint);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof UpdateParameter)) {
+            return false;
+        }
+        UpdateParameter that = (UpdateParameter) thatObject;
+
+        return updateIntervalInMinutes == that.updateIntervalInMinutes
+                && TextUtils.equals(updateMethod, that.updateMethod)
+                && TextUtils.equals(restriction, that.restriction)
+                && TextUtils.equals(serverUri, that.serverUri)
+                && TextUtils.equals(username, that.username)
+                && TextUtils.equals(base64EncodedPassword, that.base64EncodedPassword)
+                && TextUtils.equals(trustRootCertUrl, that.trustRootCertUrl)
+                && Arrays.equals(trustRootCertSha256Fingerprint,
+                        that.trustRootCertSha256Fingerprint);
+    }
+
+    /**
+     * Validate UpdateParameter data.
+     *
+     * @return true on success
+     */
+    public boolean validate() {
+        if (updateIntervalInMinutes == Long.MIN_VALUE) {
+            Log.d(TAG, "Update interval not specified");
+            return false;
+        }
+        // Update not applicable.
+        if (updateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
+            return true;
+        }
+
+        if (!TextUtils.equals(updateMethod, UPDATE_METHOD_OMADM)
+                && !TextUtils.equals(updateMethod, UPDATE_METHOD_SSP)) {
+            Log.d(TAG, "Unknown update method: " + updateMethod);
+            return false;
+        }
+
+        if (!TextUtils.equals(restriction, UPDATE_RESTRICTION_HOMESP)
+                && !TextUtils.equals(restriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
+                && !TextUtils.equals(restriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
+            Log.d(TAG, "Unknown restriction: " + restriction);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(serverUri)) {
+            Log.d(TAG, "Missing update server URI");
+            return false;
+        }
+        if (serverUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
+            Log.d(TAG, "URI bytes exceeded the max: "
+                    + serverUri.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(username)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+            Log.d(TAG, "Username bytes exceeded the max: "
+                    + username.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(base64EncodedPassword)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+            Log.d(TAG, "Password bytes exceeded the max: "
+                    + base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+        try {
+            Base64.decode(base64EncodedPassword, Base64.DEFAULT);
+        } catch (IllegalArgumentException e) {
+            Log.d(TAG, "Invalid encoding for password: " + base64EncodedPassword);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(trustRootCertUrl)) {
+            Log.d(TAG, "Missing trust root certificate URL");
+            return false;
+        }
+        if (trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+            Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
+                    + trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (trustRootCertSha256Fingerprint == null) {
+            Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
+            return false;
+        }
+        if (trustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
+            Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+                    + trustRootCertSha256Fingerprint.length);
+            return false;
+        }
+        return true;
+    }
+
+    public static final Creator<UpdateParameter> CREATOR =
+        new Creator<UpdateParameter>() {
+            @Override
+            public UpdateParameter createFromParcel(Parcel in) {
+                UpdateParameter updateParam = new UpdateParameter();
+                updateParam.updateIntervalInMinutes = in.readLong();
+                updateParam.updateMethod = in.readString();
+                updateParam.restriction = in.readString();
+                updateParam.serverUri = in.readString();
+                updateParam.username = in.readString();
+                updateParam.base64EncodedPassword = in.readString();
+                updateParam.trustRootCertUrl = in.readString();
+                updateParam.trustRootCertSha256Fingerprint = in.createByteArray();
+                return updateParam;
+            }
+
+            @Override
+            public UpdateParameter[] newArray(int size) {
+                return new UpdateParameter[size];
+            }
+        };
+}
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
index 8c1eb08..995963d 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
@@ -42,7 +42,7 @@
 a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
 eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
 QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
-VTJSbWx1WjJWeVVISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+VTJSbWx1WjJWeWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
 KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
 bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
 OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
index 6d86dd5..3ddd09f 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
@@ -35,7 +35,7 @@
 aWZpY2F0ZTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9kZT4KICAgICAgICAgICAgPE5vZGVOYW1l
 PkNlcnRpZmljYXRlVHlwZTwvTm9kZU5hbWU+CiAgICAgICAgICAgIDxWYWx1ZT54NTA5djM8L1Zh
 bHVlPgogICAgICAgICAgPC9Ob2RlPgogICAgICAgICAgPE5vZGU+CiAgICAgICAgICAgIDxOb2Rl
-TmFtZT5DZXJ0U0hBMjU2RmluZ2VyUHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
+TmFtZT5DZXJ0U0hBMjU2RmluZ2VycHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
 MWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYx
 ZjFmMWYxZjwvVmFsdWU+CiAgICAgICAgICA8L05vZGU+CiAgICAgICAgPC9Ob2RlPgogICAgICAg
 IDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPlNJTTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9k
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 3969f69..7f2d95d 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -8,6 +8,10 @@
       </Type>
     </RTProperties>
     <Node>
+      <NodeName>UpdateIdentifier</NodeName>
+      <Value>12</Value>
+    </Node>
+    <Node>
       <NodeName>i001</NodeName>
       <Node>
         <NodeName>HomeSP</NodeName>
@@ -143,7 +147,7 @@
             <Value>x509v3</Value>
           </Node>
           <Node>
-            <NodeName>CertSHA256FingerPrint</NodeName>
+            <NodeName>CertSHA256Fingerprint</NodeName>
             <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
           </Node>
         </Node>
@@ -159,6 +163,237 @@
           </Node>
         </Node>
       </Node>
+      <Node>
+        <NodeName>Policy</NodeName>
+        <Node>
+          <NodeName>PreferredRoamingPartnerList</NodeName>
+          <Node>
+            <NodeName>p001</NodeName>
+            <Node>
+              <NodeName>FQDN_Match</NodeName>
+              <Value>test1.fqdn.com,exactMatch</Value>
+            </Node>
+            <Node>
+              <NodeName>Priority</NodeName>
+              <Value>127</Value>
+            </Node>
+            <Node>
+              <NodeName>Country</NodeName>
+              <Value>us,fr</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>p002</NodeName>
+            <Node>
+              <NodeName>FQDN_Match</NodeName>
+              <Value>test2.fqdn.com,includeSubdomains</Value>
+            </Node>
+            <Node>
+              <NodeName>Priority</NodeName>
+              <Value>200</Value>
+            </Node>
+            <Node>
+              <NodeName>Country</NodeName>
+              <Value>*</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>MinBackhaulThreshold</NodeName>
+          <Node>
+            <NodeName>m001</NodeName>
+            <Node>
+              <NodeName>NetworkType</NodeName>
+              <Value>home</Value>
+            </Node>
+            <Node>
+              <NodeName>DLBandwidth</NodeName>
+              <Value>23412</Value>
+            </Node>
+            <Node>
+              <NodeName>ULBandwidth</NodeName>
+              <Value>9823</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>m002</NodeName>
+            <Node>
+              <NodeName>NetworkType</NodeName>
+              <Value>roaming</Value>
+            </Node>
+            <Node>
+              <NodeName>DLBandwidth</NodeName>
+              <Value>9271</Value>
+            </Node>
+            <Node>
+              <NodeName>ULBandwidth</NodeName>
+              <Value>2315</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>PolicyUpdate</NodeName>
+          <Node>
+            <NodeName>UpdateInterval</NodeName>
+            <Value>120</Value>
+          </Node>
+          <Node>
+            <NodeName>UpdateMethod</NodeName>
+            <Value>OMA-DM-ClientInitiated</Value>
+          </Node>
+          <Node>
+            <NodeName>Restriction</NodeName>
+            <Value>HomeSP</Value>
+          </Node>
+          <Node>
+            <NodeName>URI</NodeName>
+            <Value>policy.update.com</Value>
+          </Node>
+          <Node>
+            <NodeName>UsernamePassword</NodeName>
+            <Node>
+              <NodeName>Username</NodeName>
+              <Value>updateUser</Value>
+            </Node>
+            <Node>
+              <NodeName>Password</NodeName>
+              <Value>updatePass</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>TrustRoot</NodeName>
+            <Node>
+              <NodeName>CertURL</NodeName>
+              <Value>update.cert.com</Value>
+            </Node>
+            <Node>
+              <NodeName>CertSHA256Fingerprint</NodeName>
+              <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>SPExclusionList</NodeName>
+          <Node>
+            <NodeName>s001</NodeName>
+            <Node>
+              <NodeName>SSID</NodeName>
+              <Value>excludeSSID</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>RequiredProtoPortTuple</NodeName>
+          <Node>
+            <NodeName>r001</NodeName>
+            <Node>
+              <NodeName>IPProtocol</NodeName>
+              <Value>12</Value>
+            </Node>
+            <Node>
+              <NodeName>PortNumber</NodeName>
+              <Value>34,92,234</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>MaximumBSSLoadValue</NodeName>
+          <Value>23</Value>
+        </Node>
+      </Node>
+      <Node>
+        <NodeName>CredentialPriority</NodeName>
+        <Value>99</Value>
+      </Node>
+      <Node>
+        <NodeName>AAAServerTrustRoot</NodeName>
+        <Node>
+          <NodeName>a001</NodeName>
+          <Node>
+            <NodeName>CertURL</NodeName>
+            <Value>server1.trust.root.com</Value>
+          </Node>
+          <Node>
+            <NodeName>CertSHA256Fingerprint</NodeName>
+            <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+          </Node>
+        </Node>
+      </Node>
+      <Node>
+        <NodeName>SubscriptionUpdate</NodeName>
+        <Node>
+          <NodeName>UpdateInterval</NodeName>
+          <Value>120</Value>
+        </Node>
+        <Node>
+          <NodeName>UpdateMethod</NodeName>
+          <Value>SSP-ClientInitiated</Value>
+        </Node>
+        <Node>
+          <NodeName>Restriction</NodeName>
+          <Value>RoamingPartner</Value>
+        </Node>
+        <Node>
+          <NodeName>URI</NodeName>
+          <Value>subscription.update.com</Value>
+        </Node>
+        <Node>
+          <NodeName>UsernamePassword</NodeName>
+          <Node>
+            <NodeName>Username</NodeName>
+            <Value>subscriptionUser</Value>
+          </Node>
+          <Node>
+            <NodeName>Password</NodeName>
+            <Value>subscriptionPass</Value>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>TrustRoot</NodeName>
+          <Node>
+            <NodeName>CertURL</NodeName>
+            <Value>subscription.update.cert.com</Value>
+          </Node>
+          <Node>
+            <NodeName>CertSHA256Fingerprint</NodeName>
+            <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+          </Node>
+        </Node>
+      </Node>
+      <Node>
+        <NodeName>SubscriptionParameter</NodeName>
+        <Node>
+          <NodeName>CreationDate</NodeName>
+          <Value>2016-02-01T10:00:00Z</Value>
+        </Node>
+        <Node>
+          <NodeName>ExpirationDate</NodeName>
+          <Value>2016-03-01T10:00:00Z</Value>
+        </Node>
+        <Node>
+          <NodeName>TypeOfSubscription</NodeName>
+          <Value>Gold</Value>
+        </Node>
+        <Node>
+          <NodeName>UsageLimits</NodeName>
+          <Node>
+            <NodeName>DataLimit</NodeName>
+            <Value>921890</Value>
+          </Node>
+          <Node>
+            <NodeName>StartDate</NodeName>
+            <Value>2016-12-01T10:00:00Z</Value>
+          </Node>
+          <Node>
+            <NodeName>TimeLimit</NodeName>
+            <Value>120</Value>
+          </Node>
+          <Node>
+            <NodeName>UsageTimePeriod</NodeName>
+            <Value>99910</Value>
+          </Node>
+        </Node>
+      </Node>
     </Node>
   </Node>
 </MgmtTree>
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 0e503d5..fa546a5 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -87,6 +87,52 @@
     }
 
     @Test
+    public void testSetClientKeyEntryWithNull() {
+        mEnterpriseConfig.setClientKeyEntry(null, null);
+        assertEquals(null, mEnterpriseConfig.getClientCertificateChain());
+        assertEquals(null, mEnterpriseConfig.getClientCertificate());
+        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
+        assertEquals(null, mEnterpriseConfig.getClientCertificateChain());
+        assertEquals(null, mEnterpriseConfig.getClientCertificate());
+    }
+
+    @Test
+    public void testSetClientCertificateChain() {
+        PrivateKey clientKey = FakeKeys.RSA_KEY1;
+        X509Certificate cert0 = FakeKeys.CLIENT_CERT;
+        X509Certificate cert1 = FakeKeys.CA_CERT1;
+        X509Certificate[] clientChain = new X509Certificate[] {cert0, cert1};
+        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+        X509Certificate[] result = mEnterpriseConfig.getClientCertificateChain();
+        assertEquals(result.length, 2);
+        assertTrue(result[0] == cert0 && result[1] == cert1);
+        assertTrue(mEnterpriseConfig.getClientCertificate() == cert0);
+    }
+
+    private boolean isClientCertificateChainInvalid(X509Certificate[] clientChain) {
+        boolean exceptionThrown = false;
+        try {
+            PrivateKey clientKey = FakeKeys.RSA_KEY1;
+            mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+        } catch (IllegalArgumentException e) {
+            exceptionThrown = true;
+        }
+        return exceptionThrown;
+    }
+
+    @Test
+    public void testSetInvalidClientCertificateChain() {
+        X509Certificate clientCert = FakeKeys.CLIENT_CERT;
+        X509Certificate caCert = FakeKeys.CA_CERT1;
+        assertTrue("Invalid client certificate",
+                isClientCertificateChainInvalid(new X509Certificate[] {caCert, caCert}));
+        assertTrue("Invalid CA certificate",
+                isClientCertificateChainInvalid(new X509Certificate[] {clientCert, clientCert}));
+        assertTrue("Both certificates invalid",
+                isClientCertificateChainInvalid(new X509Certificate[] {caCert, clientCert}));
+    }
+
+    @Test
     public void testSaveSingleCaCertificateAlias() {
         final String alias = "single_alias 0";
         mEnterpriseConfig.setCaCertificateAliases(new String[] {alias});
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 2350d32..1eb08e0 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -22,16 +22,26 @@
 import android.net.wifi.EAPConstants;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
 
 import org.junit.Test;
 
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
 /**
  * Unit tests for {@link android.net.wifi.hotspot2.PasspointConfiguration}.
  */
 @SmallTest
 public class PasspointConfigurationTest {
+    private static final int MAX_URL_BYTES = 1023;
+    private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
 
     /**
      * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
@@ -66,6 +76,93 @@
     }
 
     /**
+     * Helper function for creating a {@link Policy} for testing.
+     *
+     * @return {@link Policy}
+     */
+    private static Policy createPolicy() {
+        Policy policy = new Policy();
+        policy.minHomeDownlinkBandwidth = 123;
+        policy.minHomeUplinkBandwidth = 345;
+        policy.minRoamingDownlinkBandwidth = 567;
+        policy.minRoamingUplinkBandwidth = 789;
+        policy.maximumBssLoadValue = 12;
+        policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+        policy.requiredProtoPortMap = new HashMap<>();
+        policy.requiredProtoPortMap.put(12, "23,342,123");
+        policy.requiredProtoPortMap.put(23, "789,372,1235");
+
+        policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "partner1.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 12;
+        partner1.countries = "us,jp";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "partner2.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 42;
+        partner2.countries = "ca,fr";
+        policy.preferredRoamingPartnerList.add(partner1);
+        policy.preferredRoamingPartnerList.add(partner2);
+
+        policy.policyUpdate = new UpdateParameter();
+        policy.policyUpdate.updateIntervalInMinutes = 1712;
+        policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        policy.policyUpdate.serverUri = "policy.update.com";
+        policy.policyUpdate.username = "username";
+        policy.policyUpdate.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+        policy.policyUpdate.trustRootCertSha256Fingerprint =
+                new byte[CERTIFICATE_FINGERPRINT_BYTES];
+
+        return policy;
+    }
+
+    private static UpdateParameter createSubscriptionUpdate() {
+        UpdateParameter subUpdate = new UpdateParameter();
+        subUpdate.updateIntervalInMinutes = 9021;
+        subUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+        subUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+        subUpdate.serverUri = "subscription.update.com";
+        subUpdate.username = "subUsername";
+        subUpdate.base64EncodedPassword =
+                Base64.encodeToString("subPassword".getBytes(), Base64.DEFAULT);
+        subUpdate.trustRootCertUrl = "subscription.trust.cert.com";
+        subUpdate.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_FINGERPRINT_BYTES];
+        return subUpdate;
+    }
+    /**
+     * Helper function for creating a {@link PasspointConfiguration} for testing.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private static PasspointConfiguration createConfig() {
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.homeSp = createHomeSp();
+        config.credential = createCredential();
+        config.policy = createPolicy();
+        config.subscriptionUpdate = createSubscriptionUpdate();
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("trustRoot.cert1.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        config.trustRootCertList.put("trustRoot.cert2.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        config.updateIdentifier = 1;
+        config.credentialPriority = 120;
+        config.subscriptionCreationTimeInMs = 231200;
+        config.subscriptionExpirationTimeInMs = 2134232;
+        config.subscriptionType = "Gold";
+        config.usageLimitUsageTimePeriodInMinutes = 3600;
+        config.usageLimitStartTimeInMs = 124214213;
+        config.usageLimitDataLimit = 14121;
+        config.usageLimitTimeLimitInMinutes = 78912;
+        return config;
+    }
+
+    /**
      * Verify parcel write and read consistency for the given configuration.
      *
      * @param writeConfig The configuration to verify
@@ -92,39 +189,73 @@
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained both HomeSP and Credential.
+     * Verify parcel read/write for a configuration that contained the full configuration.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithHomeSPAndCredential() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
-        config.credential = createCredential();
+    public void verifyParcelWithFullConfiguration() throws Exception {
+        verifyParcel(createConfig());
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain HomeSP.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutHomeSP() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.homeSp = null;
         verifyParcel(config);
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained only HomeSP.
+     * Verify parcel read/write for a configuration that doesn't contain Credential.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithHomeSPOnly() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
+    public void verifyParcelWithoutCredential() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.credential = null;
         verifyParcel(config);
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained only Credential.
+     * Verify parcel read/write for a configuration that doesn't contain Policy.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithCredentialOnly() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.credential = createCredential();
+    public void verifyParcelWithoutPolicy() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.policy = null;
+        verifyParcel(config);
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain subscription update.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutSubscriptionUpdate() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.subscriptionUpdate = null;
+        verifyParcel(config);
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain trust root certificate
+     * list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutTrustRootCertList() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.trustRootCertList = null;
         verifyParcel(config);
     }
 
@@ -140,43 +271,108 @@
     }
 
     /**
+     * Verify that a configuration contained all fields is valid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateFullConfig() throws Exception {
+        PasspointConfiguration config = createConfig();
+        assertTrue(config.validate());
+    }
+
+    /**
      * Verify that a configuration without Credential is invalid.
      *
      * @throws Exception
      */
     @Test
     public void validateConfigWithoutCredential() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
+        PasspointConfiguration config = createConfig();
+        config.credential = null;
         assertFalse(config.validate());
     }
 
     /**
-     * Verify that a a configuration without HomeSP is invalid.
+     * Verify that a configuration without HomeSP is invalid.
      *
      * @throws Exception
      */
     @Test
     public void validateConfigWithoutHomeSp() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.credential = createCredential();
+        PasspointConfiguration config = createConfig();
+        config.homeSp = null;
         assertFalse(config.validate());
     }
 
     /**
-     * Verify a valid configuration.
+     * Verify that a configuration without Policy is valid, since Policy configurations
+     * are optional (applied for Hotspot 2.0 Release only).
      *
      * @throws Exception
      */
     @Test
-    public void validateValidConfig() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
-        config.credential = createCredential();
+    public void validateConfigWithoutPolicy() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.policy = null;
         assertTrue(config.validate());
     }
 
     /**
+     * Verify that a configuration without subscription update is valid, since subscription
+     * update configurations are optional (applied for Hotspot 2.0 Release only).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateConfigWithoutSubscriptionUpdate() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.subscriptionUpdate = null;
+        assertTrue(config.validate());
+    }
+
+    /**
+     * Verify that a configuration with a trust root certificate URL exceeding the max size
+     * is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateConfigWithInvalidTrustRootCertUrl() throws Exception {
+        PasspointConfiguration config = createConfig();
+        byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+        Arrays.fill(rawUrlBytes, (byte) 'a');
+        config.trustRootCertList.put(new String(rawUrlBytes, StandardCharsets.UTF_8),
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        assertFalse(config.validate());
+
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put(null, new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        assertFalse(config.validate());
+    }
+
+    /**
+     * Verify that a configuration with an invalid trust root certificate fingerprint is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateConfigWithInvalidTrustRootCertFingerprint() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES + 1]);
+        assertFalse(config.validate());
+
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES - 1]);
+        assertFalse(config.validate());
+
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("test.cert.com", null);
+        assertFalse(config.validate());
+    }
+
+    /**
      * Verify that copy constructor works when pass in a null source.
      *
      * @throws Exception
@@ -195,9 +391,7 @@
      */
     @Test
     public void validateCopyConstructorWithValidSource() throws Exception {
-        PasspointConfiguration sourceConfig = new PasspointConfiguration();
-        sourceConfig.homeSp = createHomeSp();
-        sourceConfig.credential = createCredential();
+        PasspointConfiguration sourceConfig = createConfig();
         PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
         assertTrue(copyConfig.equals(sourceConfig));
     }
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
index 1c7508e..055204c 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -23,7 +23,10 @@
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 
 import org.junit.Test;
 
@@ -33,6 +36,7 @@
 import java.io.InputStreamReader;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 
@@ -81,7 +85,38 @@
      * @return {@link PasspointConfiguration}
      */
     private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
         PasspointConfiguration config = new PasspointConfiguration();
+        config.updateIdentifier = 12;
+        config.credentialPriority = 99;
+
+        // AAA Server trust root.
+        config.trustRootCertList = new HashMap<>();
+        byte[] certFingerprint = new byte[32];
+        Arrays.fill(certFingerprint, (byte) 0x1f);
+        config.trustRootCertList.put("server1.trust.root.com", certFingerprint);
+
+        // Subscription update.
+        config.subscriptionUpdate = new UpdateParameter();
+        config.subscriptionUpdate.updateIntervalInMinutes = 120;
+        config.subscriptionUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+        config.subscriptionUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+        config.subscriptionUpdate.serverUri = "subscription.update.com";
+        config.subscriptionUpdate.username = "subscriptionUser";
+        config.subscriptionUpdate.base64EncodedPassword = "subscriptionPass";
+        config.subscriptionUpdate.trustRootCertUrl = "subscription.update.cert.com";
+        config.subscriptionUpdate.trustRootCertSha256Fingerprint = new byte[32];
+        Arrays.fill(config.subscriptionUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
+        // Subscription parameters.
+        config.subscriptionCreationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
+        config.subscriptionExpirationTimeInMs = format.parse("2016-03-01T10:00:00Z").getTime();
+        config.subscriptionType = "Gold";
+        config.usageLimitDataLimit = 921890;
+        config.usageLimitStartTimeInMs = format.parse("2016-12-01T10:00:00Z").getTime();
+        config.usageLimitTimeLimitInMinutes = 120;
+        config.usageLimitUsageTimePeriodInMinutes = 99910;
 
         // HomeSP configuration.
         config.homeSp = new HomeSP();
@@ -97,7 +132,6 @@
         config.homeSp.otherHomePartners = new String[] {"other.fqdn.com"};
 
         // Credential configuration.
-        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
         config.credential = new Credential();
         config.credential.creationTimeInMs = format.parse("2016-01-01T10:00:00Z").getTime();
         config.credential.expirationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
@@ -118,12 +152,46 @@
         config.credential.simCredential = new Credential.SimCredential();
         config.credential.simCredential.imsi = "imsi";
         config.credential.simCredential.eapType = 24;
+
+        // Policy configuration.
+        config.policy = new Policy();
+        config.policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "test1.fqdn.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 127;
+        partner1.countries = "us,fr";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "test2.fqdn.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 200;
+        partner2.countries = "*";
+        config.policy.preferredRoamingPartnerList.add(partner1);
+        config.policy.preferredRoamingPartnerList.add(partner2);
+        config.policy.minHomeDownlinkBandwidth = 23412;
+        config.policy.minHomeUplinkBandwidth = 9823;
+        config.policy.minRoamingDownlinkBandwidth = 9271;
+        config.policy.minRoamingUplinkBandwidth = 2315;
+        config.policy.excludedSsidList = new String[] {"excludeSSID"};
+        config.policy.requiredProtoPortMap = new HashMap<>();
+        config.policy.requiredProtoPortMap.put(12, "34,92,234");
+        config.policy.maximumBssLoadValue = 23;
+        config.policy.policyUpdate = new UpdateParameter();
+        config.policy.policyUpdate.updateIntervalInMinutes = 120;
+        config.policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        config.policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        config.policy.policyUpdate.serverUri = "policy.update.com";
+        config.policy.policyUpdate.username = "updateUser";
+        config.policy.policyUpdate.base64EncodedPassword = "updatePass";
+        config.policy.policyUpdate.trustRootCertUrl = "update.cert.com";
+        config.policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+        Arrays.fill(config.policy.policyUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
         return config;
     }
 
     /**
-     * Parse and verify all supported fields under PPS MO tree (currently only fields under
-     * HomeSP and Credential subtree).
+     * Parse and verify all supported fields under PPS MO tree.
      *
      * @throws Exception
      */
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
new file mode 100644
index 0000000..c371c49
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.Policy}.
+ */
+@SmallTest
+public class PolicyTest {
+    private static final int MAX_NUMBER_OF_EXCLUDED_SSIDS = 128;
+    private static final int MAX_SSID_BYTES = 32;
+    private static final int MAX_PORT_STRING_BYTES = 64;
+
+    /**
+     * Helper function for creating a {@link Policy} for testing.
+     *
+     * @return {@link Policy}
+     */
+    private static Policy createPolicy() {
+        Policy policy = new Policy();
+        policy.minHomeDownlinkBandwidth = 123;
+        policy.minHomeUplinkBandwidth = 345;
+        policy.minRoamingDownlinkBandwidth = 567;
+        policy.minRoamingUplinkBandwidth = 789;
+        policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+        policy.requiredProtoPortMap = new HashMap<>();
+        policy.requiredProtoPortMap.put(12, "23,342,123");
+        policy.requiredProtoPortMap.put(23, "789,372,1235");
+        policy.maximumBssLoadValue = 12;
+
+        policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "partner1.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 12;
+        partner1.countries = "us,jp";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "partner2.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 42;
+        partner2.countries = "ca,fr";
+        policy.preferredRoamingPartnerList.add(partner1);
+        policy.preferredRoamingPartnerList.add(partner2);
+
+        policy.policyUpdate = new UpdateParameter();
+        policy.policyUpdate.updateIntervalInMinutes = 1712;
+        policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        policy.policyUpdate.serverUri = "policy.update.com";
+        policy.policyUpdate.username = "username";
+        policy.policyUpdate.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+        policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+
+        return policy;
+    }
+
+    /**
+     * Helper function for verifying Policy after parcel write then read.
+     * @param policyToWrite
+     * @throws Exception
+     */
+    private static void verifyParcel(Policy policyToWrite) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        policyToWrite.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        Policy policyFromRead = Policy.CREATOR.createFromParcel(parcel);
+        assertTrue(policyFromRead.equals(policyToWrite));
+    }
+
+    /**
+     * Verify parcel read/write for an empty Policy.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithEmptyPolicy() throws Exception {
+        verifyParcel(new Policy());
+    }
+
+    /**
+     * Verify parcel read/write for a Policy with all fields set.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithFullPolicy() throws Exception {
+        verifyParcel(createPolicy());
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without protocol port map.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutProtoPortMap() throws Exception {
+        Policy policy = createPolicy();
+        policy.requiredProtoPortMap = null;
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without preferred roaming partner list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutPreferredRoamingPartnerList() throws Exception {
+        Policy policy = createPolicy();
+        policy.preferredRoamingPartnerList = null;
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without policy update parameters.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.policyUpdate = null;
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify that policy created using copy constructor with null source should be the same
+     * as the policy created using default constructor.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithNullSource() throws Exception {
+        Policy copyPolicy = new Policy(null);
+        Policy defaultPolicy = new Policy();
+        assertTrue(defaultPolicy.equals(copyPolicy));
+    }
+
+    /**
+     * Verify that policy created using copy constructor with a valid source should be the
+     * same as the source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithFullPolicy() throws Exception {
+        Policy policy = createPolicy();
+        Policy copyPolicy = new Policy(policy);
+        assertTrue(policy.equals(copyPolicy));
+    }
+
+    /**
+     * Verify that a default policy (with no informatio) is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithDefault() throws Exception {
+        Policy policy = new Policy();
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy created using {@link #createPolicy} is valid, since all fields are
+     * filled in with valid values.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithFullPolicy() throws Exception {
+        assertTrue(createPolicy().validate());
+    }
+
+    /**
+     * Verify that a policy without policy update parameters is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithoutPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.policyUpdate = null;
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with invalid policy update parameters is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.policyUpdate = new UpdateParameter();
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a preferred roaming partner with FQDN not specified is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithRoamingPartnerWithoutFQDN() throws Exception {
+        Policy policy = createPolicy();
+        Policy.RoamingPartner partner = new Policy.RoamingPartner();
+        partner.fqdnExactMatch = true;
+        partner.priority = 12;
+        partner.countries = "us,jp";
+        policy.preferredRoamingPartnerList.add(partner);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a preferred roaming partner with countries not specified is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithRoamingPartnerWithoutCountries() throws Exception {
+        Policy policy = createPolicy();
+        Policy.RoamingPartner partner = new Policy.RoamingPartner();
+        partner.fqdn = "test.com";
+        partner.fqdnExactMatch = true;
+        partner.priority = 12;
+        policy.preferredRoamingPartnerList.add(partner);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a proto-port tuple that contains an invalid port string is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidPortStringInProtoPortMap() throws Exception {
+        Policy policy = createPolicy();
+        byte[] rawPortBytes = new byte[MAX_PORT_STRING_BYTES + 1];
+        policy.requiredProtoPortMap.put(324, new String(rawPortBytes, StandardCharsets.UTF_8));
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with number of excluded SSIDs exceeded the max is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithSsidExclusionListSizeExceededMax() throws Exception {
+        Policy policy = createPolicy();
+        policy.excludedSsidList = new String[MAX_NUMBER_OF_EXCLUDED_SSIDS + 1];
+        Arrays.fill(policy.excludedSsidList, "ssid");
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with an invalid SSID in the excluded SSID list is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidSsid() throws Exception {
+        Policy policy = createPolicy();
+        byte[] rawSsidBytes = new byte[MAX_SSID_BYTES + 1];
+        Arrays.fill(rawSsidBytes, (byte) 'a');
+        policy.excludedSsidList = new String[] {new String(rawSsidBytes, StandardCharsets.UTF_8)};
+        assertFalse(policy.validate());
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
new file mode 100644
index 0000000..6bf0db1b
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
@@ -0,0 +1,348 @@
+/*
+ * 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.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.UpdateParameter}.
+ */
+@SmallTest
+public class UpdateParameterTest {
+    private static final int MAX_URI_BYTES = 1023;
+    private static final int MAX_URL_BYTES = 1023;
+    private static final int MAX_USERNAME_BYTES = 63;
+    private static final int MAX_PASSWORD_BYTES = 255;
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * Helper function for creating a {@link UpdateParameter} for testing.
+     *
+     * @return {@link UpdateParameter}
+     */
+    private static UpdateParameter createUpdateParameter() {
+        UpdateParameter updateParam = new UpdateParameter();
+        updateParam.updateIntervalInMinutes = 1712;
+        updateParam.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        updateParam.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        updateParam.serverUri = "server.pdate.com";
+        updateParam.username = "username";
+        updateParam.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        updateParam.trustRootCertUrl = "trust.cert.com";
+        updateParam.trustRootCertSha256Fingerprint = new byte[32];
+        return updateParam;
+    }
+
+    /**
+     * Helper function for verifying UpdateParameter after parcel write then read.
+     * @param paramToWrite The UpdateParamter to verify
+     * @throws Exception
+     */
+    private static void verifyParcel(UpdateParameter paramToWrite) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        paramToWrite.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        UpdateParameter paramFromRead = UpdateParameter.CREATOR.createFromParcel(parcel);
+        assertTrue(paramFromRead.equals(paramToWrite));
+    }
+
+    /**
+     * Verify parcel read/write for an empty UpdateParameter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithEmptyUpdateParameter() throws Exception {
+        verifyParcel(new UpdateParameter());
+    }
+
+    /**
+     * Verify parcel read/write for a UpdateParameter with all fields set.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithFullUpdateParameter() throws Exception {
+        verifyParcel(createUpdateParameter());
+    }
+
+    /**
+     * Verify that UpdateParameter created using copy constructor with null source should be the
+     * same as the UpdateParameter created using default constructor.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithNullSource() throws Exception {
+        UpdateParameter copyParam = new UpdateParameter(null);
+        UpdateParameter defaultParam = new UpdateParameter();
+        assertTrue(defaultParam.equals(copyParam));
+    }
+
+    /**
+     * Verify that UpdateParameter created using copy constructor with a valid source should be the
+     * same as the source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithFullUpdateParameter() throws Exception {
+        UpdateParameter origParam = createUpdateParameter();
+        UpdateParameter copyParam = new UpdateParameter(origParam);
+        assertTrue(origParam.equals(copyParam));
+    }
+
+    /**
+     * Verify that a default UpdateParameter is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithDefault() throws Exception {
+        UpdateParameter updateParam = new UpdateParameter();
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter created using {@link #createUpdateParameter} is valid,
+     * since all fields are filled in with valid values.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithFullPolicy() throws Exception {
+        assertTrue(createUpdateParameter().validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an unknown update method is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUnknowMethod() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.updateMethod = "adsfasd";
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an unknown restriction is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUnknowRestriction() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.restriction = "adsfasd";
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an username exceeding maximum size is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUsernameExceedingMaxSize() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUsernameBytes = new byte[MAX_USERNAME_BYTES + 1];
+        Arrays.fill(rawUsernameBytes, (byte) 'a');
+        updateParam.username = new String(rawUsernameBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an empty username is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithEmptyUsername() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.username = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with a password exceeding maximum size is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithPasswordExceedingMaxSize() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawPasswordBytes = new byte[MAX_PASSWORD_BYTES + 1];
+        Arrays.fill(rawPasswordBytes, (byte) 'a');
+        updateParam.base64EncodedPassword = new String(rawPasswordBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an empty password is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithEmptyPassword() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.base64EncodedPassword = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with a Base64 encoded password that contained invalid padding
+     * is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithPasswordContainedInvalidPadding() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.base64EncodedPassword = updateParam.base64EncodedPassword + "=";
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without trust root certificate URL is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutTrustRootCertUrl() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.trustRootCertUrl = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with invalid trust root certificate URL is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithInvalidTrustRootCertUrl() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+        Arrays.fill(rawUrlBytes, (byte) 'a');
+        updateParam.trustRootCertUrl = new String(rawUrlBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without trust root certificate SHA-256 fingerprint is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithouttrustRootCertSha256Fingerprint() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.trustRootCertSha256Fingerprint = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an incorrect size trust root certificate SHA-256
+     * fingerprint is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithInvalidtrustRootCertSha256Fingerprint() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES + 1];
+        assertFalse(updateParam.validate());
+
+        updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES - 1];
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without server URI is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutServerUri() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.serverUri = null;
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an invalid server URI is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidServerUri() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUriBytes = new byte[MAX_URI_BYTES + 1];
+        Arrays.fill(rawUriBytes, (byte) 'a');
+        updateParam.serverUri = new String(rawUriBytes, StandardCharsets.UTF_8);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with update interval set to "never" will not perform
+     * validation on other parameters, since update is not applicable in this case.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithNoServerCheck() throws Exception {
+        UpdateParameter updateParam = new UpdateParameter();
+        updateParam.updateIntervalInMinutes = UpdateParameter.UPDATE_CHECK_INTERVAL_NEVER;
+        updateParam.username = null;
+        updateParam.base64EncodedPassword = null;
+        updateParam.updateMethod = null;
+        updateParam.restriction = null;
+        updateParam.serverUri = null;
+        updateParam.trustRootCertUrl = null;
+        updateParam.trustRootCertSha256Fingerprint = null;
+        assertTrue(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with unset update interval is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutUpdateInterval() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.updateIntervalInMinutes = Long.MIN_VALUE;
+        assertFalse(updateParam.validate());
+    }
+}